mirror of
https://github.com/HackTricks-wiki/hacktricks-cloud.git
synced 2026-07-28 22:51:09 -07:00
Translated ['', 'src/pentesting-ci-cd/github-security/abusing-github-act
This commit is contained in:
+69
-25
@@ -4,16 +4,16 @@
|
||||
|
||||
## Entendendo o risco
|
||||
|
||||
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 renderiza expressões ${{ ... }} antes de a step executar. O valor renderizado é colado no programa da step (para run steps, um shell script). Se você interpolar input não confiável diretamente dentro de run:, o atacante controla parte do shell program e pode executar comandos arbitrários.
|
||||
|
||||
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
|
||||
|
||||
Pontos-chave:
|
||||
- A renderização ocorre antes da execução. O script de run é gerado com todas as expressões resolvidas e então executado pelo shell.
|
||||
- Muitos contexts contêm campos controlados pelo usuário dependendo do evento que dispara (issues, PRs, comments, discussions, forks, stars, etc.). Veja a referência de untrusted input: https://securitylab.github.com/resources/github-actions-untrusted-input/
|
||||
- Shell quoting dentro de run: não é uma defesa confiável, porque a injeção acontece na fase de renderização do template. Atacantes podem sair das aspas ou injetar operadores via input maliciosamente construído.
|
||||
- A renderização acontece antes da execução. O run script é gerado com todas as expressões resolvidas e então executado pelo shell.
|
||||
- Muitos contexts contêm campos controlados pelo usuário dependendo do evento que dispara a ação (issues, PRs, comments, discussions, forks, stars, etc.). Veja a referência de untrusted input: https://securitylab.github.com/resources/github-actions-untrusted-input/
|
||||
- Shell quoting dentro de run: não é uma defesa confiável, porque a injection ocorre na etapa de template rendering. Attackers podem sair de quotes ou injetar operators via input criado para isso.
|
||||
|
||||
## Vulnerable pattern → RCE on runner
|
||||
## Padrão vulnerável → RCE on runner
|
||||
|
||||
Workflow vulnerável (disparado quando alguém abre uma nova issue):
|
||||
```yaml
|
||||
@@ -36,7 +36,7 @@ with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
labels: new
|
||||
```
|
||||
Se um atacante abrir uma issue com o título $(id), o step renderizado torna-se:
|
||||
Se um atacante abrir um issue intitulado $(id), o step renderizado se torna:
|
||||
```sh
|
||||
echo "New issue $(id) created"
|
||||
```
|
||||
@@ -44,12 +44,48 @@ A substituição de comando executa id no runner. Exemplo de saída:
|
||||
```
|
||||
New issue uid=1001(runner) gid=118(docker) groups=118(docker),4(adm),100(users),999(systemd-journal) created
|
||||
```
|
||||
Por que colocar entre aspas não te salva:
|
||||
- As expressões são renderizadas primeiro, depois o script resultante é executado. Se o valor não confiável contiver $(...), `;`, `"`/`'`, ou quebras de linha, ele pode alterar a estrutura do programa apesar das suas aspas.
|
||||
Por que aspas não salvam você:
|
||||
- Expressions são renderizadas primeiro, depois o script resultante é executado. Se o valor não confiável contiver $(...), `;`, `"`/`'`, ou quebras de linha, ele pode alterar a estrutura do programa apesar das aspas.
|
||||
|
||||
## Padrão seguro (shell variables via env)
|
||||
## Confusão de estado de comentário: comentários spoofed de bot → shell injection
|
||||
|
||||
Mitigação correta: copie a entrada não confiável para uma variável de ambiente, depois use a expansão nativa do shell ($VAR) no run script. Não re-incorpore com ${{ ... }} dentro do comando.
|
||||
Uma variante perigosa aparece quando um workflow **procura comments e depois trata o comentário retornado como estado confiável de automação**. Por exemplo, `peter-evans/find-comment` pode pesquisar por `body-includes` e expor o `comment-body` correspondente como output de uma step. Se o workflow **não** restringir também `comment-author`, qualquer usuário que possa comentar pode spoof the marker text esperado de um 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:"
|
||||
```
|
||||
Se essa saída for posteriormente incorporada na sintaxe do shell, o workflow se torna explorável, mesmo que a fonte original fosse "apenas um comentário":
|
||||
```yaml
|
||||
- run: |
|
||||
if [ '${{ steps.fc.outputs.comment-body }}' = '' ]; then
|
||||
echo "new issue needed"
|
||||
fi
|
||||
```
|
||||
Um atacante pode postar um comentário que:
|
||||
- corresponda à string marker pesquisada, e
|
||||
- contenha conteúdo que quebre o shell, como `' ]; <cmd>; if [ 'x`
|
||||
|
||||
Depois que o GitHub renderiza `${{ ... }}`, o Bash recebe sintaxe controlada pelo atacante, não dados. Isso cria um **exploit em duas etapas**:
|
||||
1. **Confusion of provenance**: o workflow confunde comentários do atacante com estado do bot.
|
||||
2. **Script injection**: o `comment-body` retornado é colado em `run:` e executado.
|
||||
|
||||
### TOCTOU race against bot comments
|
||||
|
||||
Se o comentário legítimo do bot só for criado após alguma etapa anterior, um atacante pode ganhar a corrida postando primeiro o comentário falsificado. Se a ação de busca retornar o comentário do atacante antes de o comentário real do bot existir (ou antes de ele ser selecionado), um comentarista público com baixo privilégio pode transformar um workflow `issue_comment`/`issue` em execução privilegiada no runner.
|
||||
|
||||
### Safer patterns for comment-driven automation
|
||||
|
||||
- Ao usar `find-comment`, exija **tanto conteúdo quanto provenance** (`comment-author`, identidade do repository/App, ou outro vínculo forte).
|
||||
- Não use comentários como estado se um label, artifact, campo da issue ou datastore externo puder armazenar o mesmo estado com mais segurança.
|
||||
- Nunca cole `comment-body`, títulos de issue, labels ou qualquer saída do workflow derivada deles diretamente em `run:`.
|
||||
- Se você precisar consumir texto de comentário, passe-o via `env:` ou um file e trate-o apenas como dados.
|
||||
|
||||
## Safe pattern (shell variables via env)
|
||||
|
||||
Mitigação correta: copie a entrada não confiável para uma environment variable, e então use a expansão nativa do shell ($VAR) no run script. Não re-incorpore com ${{ ... }} dentro do comando.
|
||||
```yaml
|
||||
# safe
|
||||
jobs:
|
||||
@@ -62,32 +98,40 @@ TITLE: ${{ github.event.issue.title }}
|
||||
run: |
|
||||
echo "New issue $TITLE created"
|
||||
```
|
||||
Notas:
|
||||
- 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:.
|
||||
Notes:
|
||||
- Evite usar ${{ env.TITLE }} dentro de run:. Isso reintroduz a renderização de template de volta ao command e traz o mesmo risco de injection.
|
||||
- Prefira passar inputs não confiáveis via env: mapping e referenciá-los com $VAR em run:.
|
||||
|
||||
## Reader-triggerable surfaces (treat as untrusted)
|
||||
## Reader-triggerable surfaces (trate como não confiáveis)
|
||||
|
||||
Contas com apenas permissão de leitura em repositórios públicos ainda podem acionar muitos eventos. Qualquer campo em contexts derivados desses eventos deve ser considerado controlado pelo atacante, a menos que se prove o contrário. Exemplos:
|
||||
Accounts com apenas permissão de leitura em repositórios públicos ainda podem acionar muitos events. Qualquer campo em contexts derivados desses events deve ser considerado controlado por attacker, a menos que se prove o contrário. Examples:
|
||||
- issues, issue_comment
|
||||
- discussion, discussion_comment (orgs can restrict discussions)
|
||||
- discussion, discussion_comment (orgs podem restringir discussions)
|
||||
- pull_request, pull_request_review, pull_request_review_comment
|
||||
- pull_request_target (dangerous if misused, runs in base repo context)
|
||||
- fork (anyone can fork public repos)
|
||||
- watch (starring a repo)
|
||||
- Indirectly via workflow_run/workflow_call chains
|
||||
- pull_request_target (perigoso se usado incorretamente, executa no contexto do base repo)
|
||||
- fork (qualquer pessoa pode fazer fork de repositórios públicos)
|
||||
- watch (marcar um repo com star)
|
||||
- Indiretamente via workflow_run/workflow_call chains
|
||||
|
||||
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/
|
||||
Quais campos específicos são controlados por attacker é algo específico de cada event. Consulte o guia de untrusted input do GitHub Security Lab: https://securitylab.github.com/resources/github-actions-untrusted-input/
|
||||
|
||||
## Local validation without touching the target repo
|
||||
|
||||
Você pode reproduzir muitas script injections de GitHub Actions com segurança usando [`act`](https://github.com/nektos/act): gere um synthetic event JSON, execute o vulnerable workflow localmente e substitua a saída da external action por um valor controlado (por example, um `comment-body` mockado). Isso é útil para depurar a payload structure, verificar se o texto injected ainda deixa uma sintaxe válida de Bash e confirmar uma harmless canary exfiltration antes de qualquer 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.
|
||||
- Minimize o uso de expressions dentro de run:. Prefira env: mapping + $VAR.
|
||||
- Se precisar transformar input, faça isso no shell usando safe tools (printf %q, jq -r, etc.), ainda começando a partir de uma shell variable.
|
||||
- Tenha cuidado extra ao interpolar branch names, PR titles, usernames, labels, discussion titles e PR head refs em scripts, command-line flags ou file paths.
|
||||
- Para reusable workflows e composite actions, aplique o mesmo padrão: mapear para env e então referenciar $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