flawopen.com/Teardowns/cve-2024-32002-git-submodule-rce
CVE-2024-32002: Git サブモジュールのシンボリックリンクによるリモートコード実行 (RCE)
CVE-2024-32002 では、細工された Git リポジトリが再帰 clone 中に大文字・小文字の衝突を利用したシンボリックリンク経由で .git に書き込み、確認前に hook を実行できました。Git の upstream 修正は clone 前後にサブモジュールの配置先を検査し、想定外の内容があれば中止します。修正版 Git に更新してください。
倉庫の案内板が A と a を同じ入口として扱うと想像してください。サブモジュールの経路が記録室(.git)へ誘導する案内板を通ると、棚に置くはずのファイルが Git の管理記録に入るおそれがあります。Git の修正は clone の前後に配置先を確認し、想定外の内容があれば停止します。
主要な概念と専門用語
Git サブモジュール- 親リポジトリのサブディレクトリとして埋め込まれた独立した Git リポジトリ。
シンボリックリンク- パスの参照先を別のターゲットへリダイレクトするファイルシステムオブジェクト。
大文字小文字の衝突 (Case Collision)- NTFS や APFS において、大文字小文字の違いのみのパスが同一ファイルを参照する動作。
Git フック.git/hooks/に配置され、チェックアウト完了時に自動実行されるシェルスクリプト。
フレームワーク対応
根本原因の分析 (Root Cause)
シンボリックリンクに対応した大文字・小文字を区別しないファイルシステムでは、細工されたリポジトリが、大小文字の折り畳み後にシンボリックリンクと衝突するサブモジュールパスを指定できました。再帰 clone 中に Git が clone 用に作成したディレクトリをそのリンクで置き換え、サブモジュールの worktree 外にある .git へファイルを書き込む可能性がありました。そこに置かれた hook は、ユーザーが checkout を確認する前に実行され得ます。upstream パッチは既存かつ空でないサブモジュールの配置先への clone を拒否し、clone 後に .git メタデータ以外を含む配置先も拒否します。
ステップ・バイ・ステップの攻撃フロー
再帰 clone がリポジトリ管理のメタデータを処理する
ユーザーが、シンボリックリンク対応の大文字・小文字を区別しないファイルシステム上で、信頼できないリポジトリを再帰 clone します。リポジトリには、大小文字の折り畳み後に衝突するサブモジュールパスと追跡対象が含まれています。
パスの衝突が信頼境界を越える
Git は衝突したサブモジュールパスを、想定された worktree ではなくシンボリックリンク経由で解決する可能性があります。影響を受ける境界は、再帰的なサブモジュール clone 中の Git のファイルシステムパス処理です。
ファイルが Git メタデータへ書き込まれる
リダイレクトされた書き込みにより、リポジトリの .git 領域に hook が置かれる可能性があります。Git プロジェクトのアドバイザリは、clone の実行中に hook が書き込まれ実行され得ると説明しています。
確認前に clone がコードを実行し得る
Git は clone 処理の一環として hook を実行するため、ユーザーが checkout の内容を確認する前にコードが動く可能性があります。修正版は配置先を検査し、想定外のファイルがあれば中止します。
ソースコード比較:脆弱 vs 堅牢化
これは clone_submodule() における Git upstream の C 変更の抜粋であり、単独で使える関数ではありません。修正前は、ここに示す clone 経路に配置先内容の対応する検査がありませんでした。修正は既存かつ空でない checkout ディレクトリを拒否し、clone 後に .git メタデータだけが存在することを確認します。補助関数がディレクトリエントリを検査します。完全な実装は upstream commit にあります。
if (!file_exists(sm_gitdir)) {
if (safe_create_leading_directories_const(sm_gitdir) < 0)
die(_("could not create directory '%s'"), sm_gitdir);
if (run_command(&cp))
die(_("clone of '%s' into submodule path '%s' failed"),
clone_data->url, clone_data_path);
}
struct stat st;
if (!file_exists(sm_gitdir)) {
if (clone_data->require_init && !stat(clone_data_path, &st) &&
!is_empty_dir(clone_data_path))
die(_("directory not empty: '%s'"), clone_data_path);
if (safe_create_leading_directories_const(sm_gitdir) < 0)
die(_("could not create directory '%s'"), sm_gitdir);
if (run_command(&cp))
die(_("clone of '%s' into submodule path '%s' failed"),
clone_data->url, clone_data_path);
if (clone_data->require_init && !stat(clone_data_path, &st) &&
!dir_contains_only_dotgit(clone_data_path)) {
char *dot_git = xstrfmt("%s/.git", clone_data_path);
unlink(dot_git);
free(dot_git);
die(_("directory not empty: '%s'"), clone_data_path);
}
}
エンジニアリング&システム堅牢化チェックリスト
- ✓サブモジュール衝突ガードを含む Git 2.45.1、2.44.1、2.43.4 以降に更新する。
- ✓信頼できないリポジトリでは自動の再帰的サブモジュールクローンを無効にする(git clone --no-recurse-submodules)。
- ✓継続的インテグレーションのランナーとビルドボットでファイルパス衝突チェックを強制する。
- ✓マルチプラットフォームの開発環境で core.protectNTFS と core.protectHFS を true に設定する。
- ✓リポジトリのチェックアウトを監査し、内部 .git ディレクトリを狙う不審なシンボリックリンクを探す。