GitPythonでは、サブモジュールの".git"パスの検証が、クローン処理後にのみ行われるため、リモートコード実行(RCE)の脆弱性が発生する可能性があります。
### 概要
`create_submodule(name, path, url)`関数は、サブモジュールをクローンする際に、リポジトリ自身の`.git`ディレクトリに直接書き込みを行った後に、パスの検証を行います。`.git/hooks`というパスが、クローン前の検証で受け入れられるため、サブモジュールは直接、アクティブなhooksディレクトリにクローンされます。その結果、サブモジュール内の`post-checkout`というファイルが、`.git/hooks/post-checkout`という実行可能なファイルとして作成されます。バージョン3.2.0以降では、後続のインデックスの検証で`ValueError`が発生しますが、これはディスクへの書き込みが完了した後であり、作成されたhookファイルは削除されません。また、バージョン3.1.xでは、このような検証は行われず、関数は問題なく実行されます。いずれの場合でも、作成されたhookは、次のGit操作(例えば、checkout)時に実行され、コードの実行を許容します。
### 詳細
checkoutパスの検証が、ディスクへの書き込みが完了した後に行われるため、書き込み前の検証という原則が守られていません。これは、コミット43a43cd8 ("Validate submodule checkout and metadata paths before mutation")で設定された、書き込み前の検証という原則に反します。
`Submodule.add()`関数は、クローン処理の前に緩やかなパス検証を行い、厳密な検証は、`_clone_repo`がディスクに書き込んだ後、インデックス作成時にのみ`.git`というエイリアスに対して行われます。
git/objects/submodule/base.py (Submodule.add)
599: path = cls._to_relative_path(repo, path) # ".git/hooks"を受け入れる
673: mrepo = cls._clone_repo(repo, url, path, name, ...) # <worktree>/.git/hooksにクローン
714: index.add([sm], write=True) # 厳密な検証はここでのみトリガーされる
git/index/base.py (IndexFile._preprocess_add_items)
732: _validate_repo_path(entry.path) # ".git"を拒否する(ただし、遅すぎる)
`_to_relative_path`は、`..`/絶対パス/リポジトリ外のパスのみをブロックし、`.git`エイリアスは、バージョン3.2.0(コミットa75aedf3)で追加された`_validate_repo_path`によってのみ拒否されます。この関数は、インデックスの境界で実行されます。732行で発生する`ValueError`は、`_clone_repo`がすでにディスクに書き込んだ内容をロールバックしません。
バージョン依存の動作:
- 3.2.0 以降のバージョン:_validate_repo_path が index.add で ValueError を発生させるが、
フックは既にディスク上に存在し、削除されない(検証とクローンの順序が誤っている)。
- 3.1.x (例: 3.1.59):_validate_repo_path が存在せず、_to_relative_path が `.git` を拒否しない。
create_submodule(path=".git/hooks", ...) がエラーなしで完了し、フックが静かにインストールされる。
### 実証
セットアップ:
evilsub/ : 実行可能ファイル "post-checkout" (ペイロード) を含む Git リポジトリのルートディレクトリ。
victim/ : 少なくとも1つのコミットと別のブランチを持つ Git リポジトリ。`.git/hooks` にはサンプルフックが存在しない(最小限の/CI チェックアウトの状態、または空のテンプレートディレクトリを使用した `git init`、または GIT_TEMPLATE_DIR/init.templateDir が空)。
import git, os.path as osp
try:
git.Repo("victim").create_submodule(name="x", path=".git/hooks", url=osp.abspath("evilsub"))
except ValueError:
pass # 3.2.0 以降: ".git" は拒否されるが、クローン後にのみ拒否されるため、すでにフックがインストールされている。
# 3.1.x: 例外は一切発生せず、呼び出しは成功し、フックがインストールされる。
#### victim/.git/hooks/post-checkout が存在し、モードが 0755 で、攻撃者が制御しており、影響を受けるすべてのバージョンで存在する。
#### 後のチェックアウトで実行される:
#### git -C victim checkout <他のブランチ> -> フックが実行される -> コード実行
実際に検証:
- 最新バージョン (3.2.0 + 3 コミット): create_submodule が git/index/base.py:732 から ValueError を発生させたが、
.git/hooks/post-checkout が存在し、実行可能であり、`git checkout` がそれを実行した。
- 3.1.59: create_submodule がエラーなしで完了し、.git/hooks/post-checkout がインストールされ、`git checkout` によって実行された。
### 影響
任意のコード実行。あるライブラリ/アプリケーションが、攻撃者によって操作された`path`からサブモジュールを作成すると、攻撃者が制御する実行ファイルが`.git/hooks`に配置され、Gitが通常の操作時にそれを実行してしまいます。これは、信頼できないデータからサブモジュールのチェックアウトパスを派生させるサービスに影響を与えます。例えば、ユーザーが提供したマニフェストやAPIリクエストからサブモジュールの仕様(名前/パス/URL)を取り込み、それを`create_submodule`に渡す場合などです。3.2.0以降では、この問題はライブラリ自身の検証機能によって引き起こされます。ライブラリは`.git`というパスを正しく拒否しますが、これはサブモジュールが既にクローンされた後に発生するため、エラーを捕捉するアプリケーションは、危険なパスがブロックされたと誤解する可能性があります。
前提条件(したがってAC:H):(1) アプリケーションが、攻撃者によって操作されたサブモジュールの`path`を渡すこと;(2) ターゲットのフック名が`.git/hooks`で未使用であること。これは、最小限の構成、CI環境、コンテナ環境、および`init.templateDir/GIT_TEMPLATE_DIR`が空の場合に当てはまります。デフォルトのサンプルフックが存在する場合、`git clone`は空でない宛先を拒否し、問題は発生しません。
影響を受けるバージョン:3.1.x (3.1.59)から現在の最新版bda18e08 (3.2.0 + 3コミット)までのすべてのテストバージョン。3.1.xでは問題が静かに発生し、3.2.0以降では、ValueErrorが発生したにもかかわらず問題が発生します。
影響を受けるパッケージ:
- pip (Python) Gitpython: 影響を受けるバージョンは3.2.0以下、3.2.1で修正済み。