FlawOpen

flawopen.com/Seguridad de GitHub Actions/Inyección de scripts en workflows

¿Cómo ocurre la inyección de scripts en GitHub Actions y cómo se evita?

Alto impacto con privilegios CWE-78 Seguridad de workflows CI/CD
En lenguaje sencillo

Un workflow puede convertir datos en sintaxis shell si inserta un título de issue o un campo de pull request en el script antes de iniciar Bash. Es como una impresora de credenciales que confunde un nombre con instrucciones. Pasa el valor mediante una variable de entorno y entrecomilla su expansión al usarlo como dato; así Bash lo recibe separado del script. El impacto depende del token, los secretos, el runner y el evento.

Contexto no confiableCampos de eventos de GitHub como títulos, cuerpos, nombres de ramas, etiquetas y mensajes de commit. Trátalos como texto controlado por terceros.
Sustitución de expresionesGitHub evalúa ${{ ... }} antes de iniciar el shell. En un bloque run: puede insertar la entrada en el código fuente del script.
Paso por entornoAsignar el valor a env: lo pasa como dato del proceso. Entrecomilla la expansión del shell y evita eval o generar otro script.
Límite de privilegiosEl evento y el workflow determinan permisos del token, secretos disponibles y acceso al runner. El impacto se limita a los recursos del job.

Causa raíz: los datos se insertan antes de analizar el shell

GitHub expande expresiones antes de iniciar el shell de un paso run. Si un título no confiable se interpola directamente en el script, sus caracteres pueden modificar el programa shell en vez de seguir siendo datos. Un valor pasado mediante env: permanece separado del texto del script; entrecomillarlo lo conserva como argumento en comandos normales. Esto no hace seguro usarlo después con eval, otro intérprete o una herramienta que trate los argumentos como código. GitHub identifica los campos de eventos no confiables como entrada controlable por atacantes.

.github/workflows/triage.yml (Interpolación insegura)Inseguro
# INSEGURO: los datos del evento forman parte del script shell generado
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 (Datos separados)Protegido
# Más seguro: pasa el valor del evento mediante el entorno
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"

Ruta de ataque resumida

Step 1

Un usuario controla un campo del evento

Por ejemplo, un título de issue público, un pull request o el nombre de una rama se convierte en entrada del workflow.

Step 2

El workflow lo inserta en el código shell

Una expresión directa ${{ github.event... }} en run: se expande antes de que Bash analice el script.

Step 3

El shell interpreta el programa alterado

Si el valor cambia la sintaxis, pueden ejecutarse comandos con la identidad del proceso del job. Si sigue siendo dato y se usa como argumento entrecomillado, no se produce esa inyección.

Step 4

Los permisos del job determinan el impacto

Un job comprometido solo alcanza los recursos expuestos a esa ejecución: permisos efectivos de GITHUB_TOKEN, secretos disponibles y recursos del runner. Revisa con cuidado los eventos de issues y pull_request_target.

Lista de endurecimiento

Fuentes primarias

Explora temas de seguridad y análisis relacionados:Directorio completo de seguridad →
Véase también Cosign GHSA-fx35-mq7g-6g98: bypass de bundle legado Dependency Confusion: riesgos y defensas Exposición del socket de Docker: riesgos y defensas