FlawOpen

flawopen.com/Segurança do GitHub Actions/Injeção de scripts em workflows

Como ocorre a injeção de scripts no GitHub Actions e como evitá-la?

Alto impacto quando privilegiado CWE-78 Segurança de workflows CI/CD
Em linguagem simples

Um workflow pode transformar dados em sintaxe shell ao inserir um título de issue ou campo de pull request no script antes de iniciar o Bash. É como uma impressora de crachás que confunde o nome com instruções. Passe o valor por uma variável de ambiente e coloque a expansão entre aspas ao usá-lo como dado; assim, o Bash recebe o valor separado do script. O impacto ainda depende do token, dos secrets, do runner e do evento.

Contexto não confiávelCampos de eventos do GitHub, como títulos, corpos, nomes de branches, labels e mensagens de commit. Trate-os como texto controlado por terceiros.
Interpolação de expressãoO GitHub avalia ${{ ... }} antes de o shell iniciar. Em um bloco run:, isso pode inserir entrada no código-fonte do script.
Passagem por ambienteMapear um valor para env: o passa como dado do processo. Coloque a expansão do shell entre aspas e evite eval ou gerar outro script.
Limite de privilégioO evento e o workflow determinam permissões do token, secrets disponíveis e acesso ao runner. O impacto depende dos recursos acessíveis ao job.

Causa raiz: dados entram antes da análise do shell

O GitHub expande expressões antes de iniciar o shell de uma etapa run. Se um título não confiável for interpolado diretamente no script, seus caracteres podem alterar o programa shell em vez de permanecerem dados. Um valor passado por env: fica separado do texto do script; entre aspas, sua expansão permanece um argumento para comandos comuns. Isso não torna seguro o uso posterior com eval, outro interpretador ou uma ferramenta que interprete argumentos como código. O GitHub documenta campos de eventos não confiáveis como entrada controlável por atacantes.

.github/workflows/triage.yml (Interpolação insegura)Inseguro
# INSEGURO: dados do evento passam a fazer parte do script shell gerado
name: Issue Triage
on:
  issues:
    types: [opened]

permissions:
  contents: read

jobs:
  triage:
    runs-on: ubuntu-latest
    steps:
      - name: Print issue title
        run: |
          printf 'Title: %s\n' "${{ github.event.issue.title }}"
.github/workflows/triage.yml (Dados separados)Protegido
# Mais seguro: passe o valor do evento pelo ambiente
name: Issue Triage
on:
  issues:
    types: [opened]

permissions:
  contents: read

jobs:
  triage:
    runs-on: ubuntu-latest
    steps:
      - name: Print issue title
        env:
          ISSUE_TITLE: ${{ github.event.issue.title }}
        run: |
          printf 'Title: %s\n' "$ISSUE_TITLE"

Caminho de ataque em resumo

Step 1

Um usuário controla um campo do evento

Por exemplo, o título de uma issue pública, o título de um pull request ou o nome de uma branch torna-se entrada do workflow.

Step 2

O workflow o insere no código-fonte shell

Uma expressão direta ${{ github.event... }} dentro de run: é expandida antes de o Bash analisar o script.

Step 3

O shell interpreta o programa alterado

Se o valor mudar a sintaxe do shell, comandos podem rodar com a identidade do processo do job. Se permanecer dado e for usado como argumento entre aspas, não há essa injeção.

Step 4

As permissões do job definem o impacto

Um job comprometido só alcança recursos expostos àquela execução, incluindo as permissões efetivas de GITHUB_TOKEN, secrets disponíveis e recursos do runner. Revise com cuidado eventos de issues e pull_request_target.

Checklist de proteção

Fontes primárias

Explore tópicos de segurança e análises relacionadas:Diretório completo de segurança →
Veja também Cosign GHSA-fx35-mq7g-6g98: bypass de bundle legado Dependency Confusion: riscos e defesas Exposição do Socket Docker: riscos e defesas