
Prueba de concepto en laboratorio que demuestra la inyección de comandos en el despacho de flujos de trabajo de GitHub Actions (CVE-2026-39866). Ejecuta las versiones vulnerable y parcheada en paralelo para un análisis educativo.
Laboratorio de prueba de concepto para inyección de comandos en GitHub Actions workflow dispatch
CVE-2026-39866 es un fallo de inyección de comandos en el workflow de lanzamiento de GitHub Actions de Lawnchair. El archivo release_update.yml pasa la entrada del usuario directamente a bash sin citarla, por lo que un atacante puede inyectar comandos. Este repositorio ejecuta tanto la versión vulnerable como la parcheada en paralelo.
| Campo | Valor |
|---|---|
| ID CVE | CVE-2026-39866 |
| GHSA | GHSA-9prc-pp2c-3427 |
| CWE | CWE-78 (Neutralización incorrecta de elementos especiales usados en un comando del sistema operativo) |
| Producto | LawnchairLauncher/lawnchair |
| Afectado | Todas las versiones anteriores a fcba413 |
| Parcheado | fcba413 |
| Descubrimiento | Abhay Kumar (@abhayclasher) |
La vulnerabilidad existe en el archivo de workflow release_update.yml. La acción toma un parámetro artifactName y lo pasa directamente a un comando bash sin citarlo.
- name: Rename artifact
run: mv ${{ inputs.artifactName }}.zip lawnchair.zip
GitHub Actions sustituye el valor de la variable en la cadena antes de que el shell la ejecute. Sin comillas alrededor de ${{ inputs.artifactName }}, el shell interpreta los espacios y puntos y comas como separadores de comandos.
Si un atacante proporciona file.zip; touch pwned.txt # como artifactName:
Cómo lo analiza GitHub Actions:
${{ inputs.artifactName }} → file.zip; touch pwned.txt #
Cómo lo ejecuta el shell:
mv file.zip; touch pwned.txt #.zip lawnchair.zip
El shell ejecuta mv file.zip, luego touch pwned.txt, e ignora el resto como comentario.
GITHUB_TOKEN o claves de firma de lanzamientos).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
Después de que el servidor se inicie, visita estas URLs:
| URL | Descripción |
|---|---|
http://localhost:3000/workflow/vulnerable | Workflow vulnerable — la inyección de comandos se ejecuta al enviar |
http://localhost:3001/workflow/patched | El mismo workflow con el parche fcba413 aplicado |
La versión vulnerable ejecutará tu comando inyectado en el runner. La versión parcheada trata la entrada como una cadena literal.
También puedes ejecutar el PoC independiente:
cd poc
node exploit.js
Esto inicia un servidor mínimo en el puerto 3000 que demuestra el mismo fallo sin el contexto completo de la aplicación.
1. El atacante controla el parámetro de entrada artifactName
2. GitHub Actions expande ${{ inputs.artifactName }} en el YAML
3. El paso run envía esto a bash
4. Bash ve caracteres especiales sin citar y los trata como sintaxis de comandos
5. Los comandos se ejecutan con los privilegios del entorno del runner
┌────────────────────────────────────────────────────┐
│ 1. Entrada maliciosa: │
│ │
│ artifactName = "file.zip; id #" │
│ │
│ 2. GitHub Actions sustituye en el YAML: │
│ │
│ run: mv file.zip; id #.zip lawnchair.zip │
│ │
│ 3. Bash ve: │
│ - mv file.zip │
│ - id <-- Comando arbitrario │
│ - #.zip lawnchair.zip (ignorado) │
│ │
│ 4. El shell ejecuta el comando arbitrario │
└────────────────────────────────────────────────────┘
La versión fcba413 introduce comillas alrededor de la variable interpolada:
- name: Rename artifact
run: mv "${{ inputs.artifactName }}.zip" lawnchair.zip
Las comillas evitan que el shell analice caracteres especiales. El payload se sigue tratando como una cadena de nombre de archivo literal.
La mejor práctica es usar variables de entorno para aislar completamente la entrada del usuario:
- name: Rename artifact
env:
ARTIFACT_NAME: ${{ inputs.artifactName }}
run: mv "$ARTIFACT_NAME.zip" lawnchair.zip
Ver el commit completo: fcba413
CVE-2026-39866/
├── README.md # Este archivo
├── docker-compose.yml # Configuración del laboratorio Docker
├── app/
│ ├── Dockerfile # Configuración de construcción del contenedor
│ ├── package.json # Dependencias de Node.js
│ └── server.js # Simulación del runner de workflow vulnerable + parcheado
└── poc/
└── exploit.js # Prueba de concepto mínima independiente
Reportado por Abhay Kumar. Este repositorio está destinado únicamente a uso educativo local. No despliegues el servidor vulnerable en una red pública.
Solo con fines educativos — usar en entornos aislados