From f4ce170fe7c9c9124b9d67baf31d2e980692c7a1 Mon Sep 17 00:00:00 2001 From: Translator Date: Mon, 6 Jul 2026 15:31:12 +0000 Subject: [PATCH] Translated ['', 'src/pentesting-ci-cd/github-security/abusing-github-act --- .../gh-actions-context-script-injections.md | 90 ++++++++++++++----- 1 file changed, 67 insertions(+), 23 deletions(-) diff --git a/src/pentesting-ci-cd/github-security/abusing-github-actions/gh-actions-context-script-injections.md b/src/pentesting-ci-cd/github-security/abusing-github-actions/gh-actions-context-script-injections.md index 06dfc961e..54a992f96 100644 --- a/src/pentesting-ci-cd/github-security/abusing-github-actions/gh-actions-context-script-injections.md +++ b/src/pentesting-ci-cd/github-security/abusing-github-actions/gh-actions-context-script-injections.md @@ -2,16 +2,16 @@ {{#include ../../../banners/hacktricks-training.md}} -## 理解风险 +## Understanding the risk -GitHub Actions 会在 step 执行前渲染表达式 ${{ ... }}。渲染后的值会被粘贴进该 step 的程序(对于 run steps,是一个 shell 脚本)。如果你在 run: 中直接插入不受信任的输入,attacker 将能控制部分 shell 程序并执行 arbitrary commands。 +GitHub Actions 在 step 执行前会渲染 expressions ${{ ... }}。渲染后的值会被粘贴进 step 的 program 中(对于 run steps,就是一个 shell script)。如果你把不受信任的输入直接插入到 run: 里,攻击者就能控制 shell program 的一部分,并执行任意 commands。 Docs: https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions and contexts/functions: https://docs.github.com/en/actions/learn-github-actions/contexts -要点: -- 渲染发生在执行之前。run 脚本会在所有表达式解析完后生成,然后由 shell 执行。 -- 许多 contexts 包含取决于触发事件的用户可控字段(issues、PRs、comments、discussions、forks、stars 等)。参见 untrusted input 参考: https://securitylab.github.com/resources/github-actions-untrusted-input/ -- 在 run: 内部对 shell 进行引号转义并不是可靠的防护,因为注入发生在模板渲染阶段。Attackers 可以通过精心构造的输入打破引号或注入操作符。 +Key points: +- Rendering 发生在 execution 之前。run script 会先生成并解析所有 expressions,然后再由 shell 执行。 +- 许多 contexts 会包含用户可控字段,具体取决于触发事件(issues、PRs、comments、discussions、forks、stars 等)。参见 untrusted input reference: https://securitylab.github.com/resources/github-actions-untrusted-input/ +- run: 里的 shell quoting 不是可靠的防护,因为 injection 发生在 template rendering 阶段。攻击者可以通过精心构造的输入跳出 quotes 或注入 operators。 ## Vulnerable pattern → RCE on runner @@ -36,20 +36,56 @@ with: github_token: ${{ secrets.GITHUB_TOKEN }} labels: new ``` -如果 attacker 打开一个标题为 $(id) 的 issue,渲染后的步骤变为: +如果攻击者打开一个标题为 $(id) 的 issue,渲染后的 step 变为: ```sh echo "New issue $(id) created" ``` -命令替换在 runner 上运行 id。示例输出: +命令替换会在 runner 上运行 id。示例输出: ``` New issue uid=1001(runner) gid=118(docker) groups=118(docker),4(adm),100(users),999(systemd-journal) created ``` -为什么引用无法保护你: -- 表达式会先被渲染,然后渲染得到的脚本会被执行。如果不受信任的值包含 $(...)、`;`、`"`/`'` 或换行,它仍能改变程序结构,即使你已做了引用。 +为什么引用也救不了你: +- 表达式会先被渲染,然后才运行生成出来的 script。若不受信任的值包含 $(...)、`;`、`"`/`'`,或换行,它仍然可以在你已经引用的情况下改变程序结构。 -## 安全模式 (shell variables via env) +## Comment-state 混淆:伪造 bot comments → shell injection -正确的缓解措施:将不受信任的输入复制到环境变量,然后在 run 脚本中使用原生 shell 展开 ($VAR)。不要在命令中用 ${{ ... }} 重新嵌入。 +一种危险变体出现在 workflow **搜索 comments,随后把返回的 comment 当作受信任的 automation state** 时。比如,`peter-evans/find-comment` 可以通过 `body-includes` 搜索,并将匹配到的 `comment-body` 作为 step output 暴露出来。如果 workflow 没有同时限制 `comment-author`,任何能够 comment 的用户都可能伪造 bot 预期的标记文本。 +```yaml +- uses: peter-evans/find-comment@v4 +id: fc +with: +issue-number: ${{ github.event.issue.number }} +body-includes: "Opened a new issue in org/repo:" +``` +如果该输出随后被嵌入到 shell 语法中,即使原始来源“只是一个注释”,该 workflow 也会变得可利用: +```yaml +- run: | +if [ '${{ steps.fc.outputs.comment-body }}' = '' ]; then +echo "new issue needed" +fi +``` +攻击者可以发布一个 comment,同时: +- 匹配被搜索的 marker string,并且 +- 包含会破坏 shell 的内容,例如 `' ]; ; if [ 'x` + +在 GitHub 渲染 `${{ ... }}` 之后,Bash 接收到的是攻击者控制的语法,而不是数据。这会造成一个**两阶段 exploit**: +1. **Provenance confusion**:workflow 把攻击者的 comments 误认为 bot state。 +2. **Script injection**:返回的 `comment-body` 被直接粘贴到 `run:` 中并执行。 + +### 针对 bot comments 的 TOCTOU race + +如果合法的 bot comment 只在某个更早的步骤之后才创建,攻击者可以通过先发布伪造的 comment 来抢先竞争。如果 search action 在真实 bot comment 出现之前(或在它被选中之前)先返回了攻击者的 comment,那么一个低权限的 public commenter 就可以把 `issue_comment`/issue workflow 变成特权 runner execution。 + +### 更安全的 comment-driven automation 模式 + +- 使用 `find-comment` 时,要求同时满足**内容和 provenance**(`comment-author`、repository/App identity,或其他强绑定)。 +- 如果 label、artifact、issue field,或 external datastore 能更安全地保存同样的 state,就不要把 comments 当作 state。 +- 永远不要把 `comment-body`、issue titles、labels,或任何从它们派生的 workflow output 直接粘贴到 `run:` 中。 +- 如果必须消费 comment text,把它通过 `env:` 或文件传递,并且只把它当作 data 处理。 + +## Safe pattern (shell variables via env) + +正确的 mitigation:把不可信输入复制到 environment variable 中,然后在 run script 里使用原生 shell expansion ($VAR)。不要在命令中再次用 ${{ ... }} 重新嵌入。 ```yaml # safe jobs: @@ -62,13 +98,13 @@ TITLE: ${{ github.event.issue.title }} run: | echo "New issue $TITLE created" ``` -注意事项: -- 避免在 run: 中使用 ${{ env.TITLE }}。那会重新将模板渲染引入命令,从而带来相同的注入风险。 -- 优先通过 env: 映射传递不受信任的输入,并在 run: 中使用 $VAR 引用它们。 +Notes: +- 避免在 run: 中使用 ${{ env.TITLE }}。这会把 template rendering 重新引入到命令中,并带来同样的 injection 风险。 +- 更推荐通过 env: mapping 传递 untrusted inputs,并在 run: 中用 $VAR 引用它们。 -## 可被读者触发的表面(视为不受信任) +## Reader-triggerable surfaces (treat as untrusted) -仅对公共仓库具有只读权限的账户仍然可以触发许多事件。由这些事件派生的 contexts 中的任何字段,除非另有证明,否则都必须被视为由攻击者控制。示例: +仅拥有 public repositories 只读权限的 accounts 仍然可以触发许多 events。除非能证明否则,所有从这些 events 派生出来的 contexts 字段都应视为 attacker-controlled。Examples: - issues, issue_comment - discussion, discussion_comment (orgs can restrict discussions) - pull_request, pull_request_review, pull_request_review_comment @@ -77,17 +113,25 @@ echo "New issue $TITLE created" - watch (starring a repo) - Indirectly via workflow_run/workflow_call chains -哪些具体字段被攻击者控制取决于事件。请参考 GitHub Security Lab’s untrusted input 指南:https://securitylab.github.com/resources/github-actions-untrusted-input/ +哪些具体字段是 attacker-controlled 取决于 event。请查阅 GitHub Security Lab 的 untrusted input guide: https://securitylab.github.com/resources/github-actions-untrusted-input/ -## 实用建议 +## Local validation without touching the target repo -- 尽量减少在 run: 中使用 expressions。优先使用 env: 映射并使用 $VAR。 -- 如果必须转换输入,请在 shell 中使用安全工具(例如 printf %q、jq -r 等)进行,且仍然应从 shell 变量开始。 -- 在将分支名、PR 标题、用户名、标签、讨论标题以及 PR head refs 插入到脚本、命令行参数或文件路径时,要格外小心。 -- 对于 reusable workflows 和 composite actions,采用相同模式:映射到 env,然后引用 $VAR。 +你可以使用 [`act`](https://github.com/nektos/act) 安全地复现许多 GitHub Actions script injections:生成一个合成的 event JSON,在本地运行有漏洞的 workflow,并将外部 action 输出替换为一个受控值(例如一个 mock 的 `comment-body`)。这对于调试 payload 结构、验证注入文本是否仍保留有效 Bash syntax,以及在任何 live test 之前确认无害的 canary exfiltration 都很有用。 + +## Practical tips + +- 尽量减少在 run: 中使用 expressions。优先使用 env: mapping + $VAR。 +- 如果必须转换 input,就在 shell 中使用安全工具(printf %q、jq -r 等)处理,但仍然从 shell variable 开始。 +- 在把 branch names、PR titles、usernames、labels、discussion titles 和 PR head refs 插入 scripts、command-line flags 或 file paths 时,要格外小心。 +- 对于 reusable workflows 和 composite actions,应用同样的模式:先映射到 env 再引用 $VAR。 ## References +- [Find Comment, Get Shell: Command Injection in dbt’s GitHub Actions](https://landh.tech/blog/20260701-find-comment-get-shell) +- [peter-evans/find-comment](https://github.com/peter-evans/find-comment) +- [GHSL-2023-109: GitHub Actions command injection in a TDesign Vue Next workflow](https://securitylab.github.com/advisories/GHSL-2023-109_TDesign_Vue_Next/) +- [nektos/act](https://github.com/nektos/act) - [GitHub Actions: A Cloudy Day for Security - Part 1](https://binarysecurity.no/posts/2025/08/securing-gh-actions-part1) - [GitHub workflow syntax](https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions) - [Contexts and expression syntax](https://docs.github.com/en/actions/learn-github-actions/contexts)