Gh Actions - Context Script Injections

Reading time: 5 minutes

tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Support HackTricks

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.

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.

Vulnerable pattern → RCE on runner

Workflow vulnerável (disparado quando alguém abre uma nova issue):

yaml
name: New Issue Created
on:
issues:
types: [opened]
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- name: New issue
run: |
echo "New issue ${{ github.event.issue.title }} created"
- name: Add "new" label to issue
uses: actions-ecosystem/action-add-labels@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
labels: new

Se um atacante abrir uma issue com o título $(id), o step renderizado torna-se:

sh
echo "New issue $(id) created"

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.

Padrão seguro (shell variables via env)

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.

yaml
# safe
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: New issue
env:
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:.

Reader-triggerable surfaces (treat as untrusted)

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:

  • issues, issue_comment
  • discussion, discussion_comment (orgs can restrict 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

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/

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

tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Support HackTricks