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:
+69
-25
@@ -4,18 +4,18 @@
|
||||
|
||||
## Розуміння ризику
|
||||
|
||||
GitHub Actions renders expressions ${{ ... }} before the step executes. The rendered value is pasted into the step’s program (for run steps, a shell script). If you interpolate untrusted input directly inside run:, the attacker controls part of the shell program and can execute arbitrary commands.
|
||||
GitHub Actions рендерить expressions ${{ ... }} до виконання step. Відрендерене значення вставляється в program step’а (для run steps, shell script). Якщо ви інтерполюєте untrusted input напряму всередині run:, attacker контролює частину shell program і може виконувати arbitrary 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
|
||||
|
||||
Ключові моменти:
|
||||
- Rendering happens before execution. The run script is generated with all expressions resolved, then executed by the shell.
|
||||
- Many contexts contain user-controlled fields depending on the triggering event (issues, PRs, comments, discussions, forks, stars, etc.). Див. untrusted input reference: https://securitylab.github.com/resources/github-actions-untrusted-input/
|
||||
- Shell quoting inside run: is not a reliable defense, because the injection occurs at the template rendering stage. Attackers can break out of quotes or inject operators via crafted input.
|
||||
Key points:
|
||||
- Rendering відбувається до execution. run script генерується з усіма resolved expressions, а потім виконується shell.
|
||||
- Багато contexts містять user-controlled fields залежно від triggering event (issues, PRs, comments, discussions, forks, stars, etc.). Див. untrusted input reference: https://securitylab.github.com/resources/github-actions-untrusted-input/
|
||||
- Shell quoting всередині run: не є надійним захистом, бо injection відбувається на стадії template rendering. Attackers можуть вийти з quotes або inject operators через crafted input.
|
||||
|
||||
## Уразливий шаблон → RCE on runner
|
||||
## Vulnerable pattern → RCE on runner
|
||||
|
||||
Вразливий workflow (triggered when someone opens a new issue):
|
||||
Vulnerable workflow (triggered when someone opens a new issue):
|
||||
```yaml
|
||||
name: New Issue Created
|
||||
on:
|
||||
@@ -36,20 +36,56 @@ with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
labels: new
|
||||
```
|
||||
Якщо зловмисник відкриє issue з назвою $(id), відрендерений крок стане:
|
||||
Якщо attacker відкриває issue з назвою $(id), відрендерений step стає:
|
||||
```sh
|
||||
echo "New issue $(id) created"
|
||||
```
|
||||
Підстановка команди виконує id на runner. Приклад виводу:
|
||||
Підстановка команд запускає id на runner. Приклад виводу:
|
||||
```
|
||||
New issue uid=1001(runner) gid=118(docker) groups=118(docker),4(adm),100(users),999(systemd-journal) created
|
||||
```
|
||||
Чому лапки не рятують:
|
||||
- Вираження обчислюються спочатку, а потім виконується отриманий скрипт. Якщо ненадійне значення містить $(...), `;`, `"`/`'` або нові рядки, воно може змінити структуру програми незважаючи на ваші лапки.
|
||||
Чому quoting не рятує:
|
||||
- Expressions рендеряться першими, а потім запускається отриманий script. Якщо untrusted value містить $(...), `;`, `"`/`'` або newlines, воно може змінити структуру program despite your quoting.
|
||||
|
||||
## Безпечний шаблон (shell variables via env)
|
||||
## Comment-state confusion: spoofed bot comments → shell injection
|
||||
|
||||
Правильне пом'якшення: скопіюйте ненадійне вхідне значення у змінну середовища, потім використовуйте нативне shell-розгортання ($VAR) у run script. Не вбудовуйте знову ${{ ... }} всередині команди.
|
||||
Небезпечний варіант з’являється, коли workflow **searches comments and later treats the returned comment as trusted automation state**. Наприклад, `peter-evans/find-comment` може шукати за `body-includes` і expose відповідний `comment-body` як step output. Якщо workflow **не** також обмежує `comment-author`, будь-який user, який може comment, може spoof the marker text, очікуваний від 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 syntax, workflow стає exploitable, навіть якщо початкове джерело було «просто коментар»:
|
||||
```yaml
|
||||
- run: |
|
||||
if [ '${{ steps.fc.outputs.comment-body }}' = '' ]; then
|
||||
echo "new issue needed"
|
||||
fi
|
||||
```
|
||||
Зловмисник може опублікувати коментар, який одночасно:
|
||||
- збігається з шуканим marker string, і
|
||||
- містить shell-breaking content, наприклад `' ]; <cmd>; if [ 'x`
|
||||
|
||||
Після того як GitHub відрендерить `${{ ... }}`, Bash отримує syntax, контрольований зловмисником, а не data. Це створює **two-stage exploit**:
|
||||
1. **Provenance confusion**: workflow помилково сприймає коментарі зловмисника за bot state.
|
||||
2. **Script injection**: повернутий `comment-body` вставляється в `run:` і виконується.
|
||||
|
||||
### TOCTOU race against bot comments
|
||||
|
||||
Якщо легітимний bot comment створюється лише після певного попереднього кроку, зловмисник може випередити його, опублікувавши підроблений коментар першим. Якщо search action повертає коментар зловмисника до того, як справжній bot comment існує (або до того, як його буде вибрано), користувач із низькими привілеями, який може публічно коментувати, здатен перетворити `issue_comment`/issue workflow на privileged runner execution.
|
||||
|
||||
### Safer patterns for comment-driven automation
|
||||
|
||||
- Під час використання `find-comment` вимагайте **і content, і provenance** (`comment-author`, repository/App identity або інший надійний binding).
|
||||
- Не використовуйте коментарі як state, якщо label, artifact, issue field або external datastore можуть безпечніше зберігати той самий state.
|
||||
- Ніколи не вставляйте `comment-body`, issue titles, labels або будь-який workflow output, похідний від них, безпосередньо в `run:`.
|
||||
- Якщо вам потрібно обробляти текст коментаря, передайте його через `env:` або файл і обробляйте лише як data.
|
||||
|
||||
## Safe pattern (shell variables via env)
|
||||
|
||||
Correct mitigation: скопіюйте untrusted input у environment variable, а потім використовуйте native shell expansion ($VAR) у run script. Не вбудовуйте його повторно за допомогою ${{ ... }} всередині command.
|
||||
```yaml
|
||||
# safe
|
||||
jobs:
|
||||
@@ -62,13 +98,13 @@ TITLE: ${{ github.event.issue.title }}
|
||||
run: |
|
||||
echo "New issue $TITLE created"
|
||||
```
|
||||
Примітки:
|
||||
- Уникайте використання ${{ env.TITLE }} всередині run:. Це знову вводить рендеринг шаблонів у команду і створює той самий ризик ін'єкції.
|
||||
- Краще передавати недовірені введення через відображення env: і звертатися до них як $VAR у run:.
|
||||
Notes:
|
||||
- Avoid using ${{ env.TITLE }} inside run:. That reintroduces template rendering back into the command and brings the same injection risk.
|
||||
- Prefer passing untrusted inputs via env: mapping and reference them with $VAR in run:.
|
||||
|
||||
## Поверхні, які може ініціювати читач (вважати ненадійними)
|
||||
## Reader-triggerable surfaces (treat as untrusted)
|
||||
|
||||
Облікові записи з правом лише на читання у публічних репозиторіях все ще можуть викликати багато подій. Будь-яке поле в контекстах, отриманих із цих подій, слід вважати контрольованим зловмисником, якщо не доведено протилежне. Приклади:
|
||||
Accounts with only read permission on public repositories can still trigger many events. Any field in contexts derived from these events must be considered attacker-controlled unless proven otherwise. 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 guide: https://securitylab.github.com/resources/github-actions-untrusted-input/
|
||||
Which specific fields are attacker-controlled is event-specific. Consult GitHub Security Lab’s untrusted input guide: https://securitylab.github.com/resources/github-actions-untrusted-input/
|
||||
|
||||
## Практичні поради
|
||||
## Local validation without touching the target repo
|
||||
|
||||
- Мінімізуйте використання виразів всередині run:. Віддавайте перевагу відображенню env: + $VAR.
|
||||
- Якщо потрібно трансформувати введення, робіть це в shell, використовуючи безпечні інструменти (printf %q, jq -r, тощо), все одно починаючи з shell-змінної.
|
||||
- Будьте особливо обережні при інтерполяції імен гілок, заголовків PR, імен користувачів, labels, discussion titles та PR head refs у скрипти, параметри командного рядка або шляхи до файлів.
|
||||
- Для reusable workflows і composite actions застосовуйте той самий підхід: відобразіть у env, а потім посилайтеся на $VAR.
|
||||
You can reproduce many GitHub Actions script injections safely with [`act`](https://github.com/nektos/act): generate a synthetic event JSON, run the vulnerable workflow locally, and replace the external action output with a controlled value (for example a mocked `comment-body`). This is useful to debug payload structure, verify whether the injected text still leaves valid Bash syntax, and confirm harmless canary exfiltration before any live test.
|
||||
|
||||
## Посилання
|
||||
## Practical tips
|
||||
|
||||
- Minimize use of expressions inside run:. Prefer env: mapping + $VAR.
|
||||
- If you must transform input, do it in the shell using safe tools (printf %q, jq -r, etc.), still starting from a shell variable.
|
||||
- Be extra careful when interpolating branch names, PR titles, usernames, labels, discussion titles, and PR head refs into scripts, command-line flags, or file paths.
|
||||
- For reusable workflows and composite actions, apply the same pattern: map to env then reference $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