# Gh Actions - Context Script Injections {{#include ../../../banners/hacktricks-training.md}} ## 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 - [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) - [Untrusted input reference for GitHub Actions](https://securitylab.github.com/resources/github-actions-untrusted-input/) {{#include ../../../banners/hacktricks-training.md}}