
Prova de conceito em laboratório demonstrando injeção de comandos no dispatch de workflows do GitHub Actions (CVE-2026-39866). Executa versões vulnerável e corrigida lado a lado para análise educacional.
Laboratório de prova de conceito para injeção de comando em GitHub Actions workflow dispatch
CVE-2026-39866 é uma vulnerabilidade de injeção de comando no workflow de release do GitHub Actions do Lawnchair. O arquivo release_update.yml passa a entrada do usuário diretamente para o bash sem aspas, permitindo que um atacante injete comandos. Este repositório executa as versões vulnerável e corrigida lado a lado.
| Campo | Valor |
|---|---|
| ID CVE | CVE-2026-39866 |
| GHSA | GHSA-9prc-pp2c-3427 |
| CWE | CWE-78 (Neutralização Incorreta de Elementos Especiais usados em um Comando do SO) |
| Produto | LawnchairLauncher/lawnchair |
| Afetado | Todas as versões anteriores a fcba413 |
| Corrigido | fcba413 |
| Descoberta | Abhay Kumar (@abhayclasher) |
A vulnerabilidade existe no arquivo de workflow release_update.yml. A ação recebe um parâmetro artifactName e o passa diretamente para um comando bash sem aspas.
- name: Rename artifact
run: mv ${{ inputs.artifactName }}.zip lawnchair.zip
O GitHub Actions substitui o valor da variável na string antes de o shell executá-la. Sem aspas ao redor de ${{ inputs.artifactName }}, o shell interpreta espaços e ponto e vírgula como separadores de comando.
Se um atacante fornecer file.zip; touch pwned.txt # como artifactName:
Como o GitHub Actions interpreta:
${{ inputs.artifactName }} → file.zip; touch pwned.txt #
Como o shell executa:
mv file.zip; touch pwned.txt #.zip lawnchair.zip
O shell executa mv file.zip, depois touch pwned.txt, e ignora o restante como comentário.
GITHUB_TOKEN ou chaves de assinatura de release).git clone https://github.com/abhayclasher/CVE-2026-39866.git
cd CVE-2026-39866/app
npm install
node server.js
git clone https://github.com/abhayclasher/CVE-2026-39866.git
cd CVE-2026-39866
docker compose up -d
Após o servidor iniciar, acesse estas URLs:
| URL | Descrição |
|---|---|
http://localhost:3000/workflow/vulnerable | Workflow vulnerável — injeção de comando executa ao enviar |
http://localhost:3001/workflow/patched | Mesmo workflow com o patch fcba413 aplicado |
A versão vulnerável executará seu comando injetado no runner. A versão corrigida trata a entrada como uma string literal.
Você também pode executar o PoC autônomo:
cd poc
node exploit.js
Isso inicia um servidor mínimo na porta 3000 que demonstra o mesmo bug sem o contexto completo do aplicativo.
1. O atacante controla o parâmetro de entrada artifactName
2. O GitHub Actions expande ${{ inputs.artifactName }} no YAML
3. A etapa run envia isso para o bash
4. O bash vê caracteres especiais sem aspas e os trata como sintaxe de comando
5. Os comandos executam com os privilégios do ambiente do runner
┌────────────────────────────────────────────────────┐
│ 1. Entrada maliciosa: │
│ │
│ artifactName = "file.zip; id #" │
│ │
│ 2. O GitHub Actions substitui no YAML: │
│ │
│ run: mv file.zip; id #.zip lawnchair.zip │
│ │
│ 3. O bash vê: │
│ - mv file.zip │
│ - id <-- Comando arbitrário │
│ - #.zip lawnchair.zip (ignorado) │
│ │
│ 4. O shell executa comando arbitrário │
└────────────────────────────────────────────────────┘
A versão fcba413 introduz aspas ao redor da variável interpolada:
- name: Rename artifact
run: mv "${{ inputs.artifactName }}.zip" lawnchair.zip
As aspas impedem que o shell interprete caracteres especiais. O payload permanece tratado como uma string de nome de arquivo literal.
A melhor prática é usar variáveis de ambiente para isolar completamente a entrada do usuário:
- name: Rename artifact
env:
ARTIFACT_NAME: ${{ inputs.artifactName }}
run: mv "$ARTIFACT_NAME.zip" lawnchair.zip
Veja o commit completo: fcba413
CVE-2026-39866/
├── README.md # Este arquivo
├── docker-compose.yml # Configuração do laboratório Docker
├── app/
│ ├── Dockerfile # Configuração de build do container
│ ├── package.json # Dependências Node.js
│ └── server.js # Simulação do runner de workflow vulnerável + corrigido
└── poc/
└── exploit.js # Prova de conceito mínima autônoma
Relatado por Abhay Kumar. Este repositório destina-se apenas ao uso educacional local. Não implante o servidor vulnerável em uma rede pública.
Apenas para fins educacionais — use em ambientes isolados