From 9322cc1b29df4b4f70992900e4e2992c4fd78375 Mon Sep 17 00:00:00 2001 From: Translator Date: Mon, 6 Jul 2026 15:30:49 +0000 Subject: [PATCH] Translated ['', 'src/pentesting-ci-cd/github-security/abusing-github-act --- .../gh-actions-context-script-injections.md | 94 ++++++++++++++----- 1 file changed, 69 insertions(+), 25 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 5e199a568..b0f918e36 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 @@ -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 `' ]; ; 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)