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
- Confira os planos de assinatura!
- Junte-se ao 💬 grupo do Discord ou ao grupo do telegram ou siga-nos no Twitter 🐦 @hacktricks_live.
- Compartilhe truques de hacking enviando PRs para o HackTricks e HackTricks Cloud repositórios do github.
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):
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:
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.
# 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
- GitHub Actions: A Cloudy Day for Security - Part 1
- GitHub workflow syntax
- Contexts and expression syntax
- Untrusted input reference for GitHub Actions
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
- Confira os planos de assinatura!
- Junte-se ao 💬 grupo do Discord ou ao grupo do telegram ou siga-nos no Twitter 🐦 @hacktricks_live.
- Compartilhe truques de hacking enviando PRs para o HackTricks e HackTricks Cloud repositórios do github.
HackTricks Cloud