mirror of
https://github.com/HackTricks-wiki/hacktricks-cloud.git
synced 2026-07-28 14:47:17 -07:00
Translated ['', 'src/pentesting-ci-cd/github-security/abusing-github-act
This commit is contained in:
+67
-23
@@ -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 的内容,例如 `' ]; <cmd>; 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)
|
||||
|
||||
Reference in New Issue
Block a user