FlawOpen

flawopen.com/Sécurité GitHub Actions/Injection de scripts dans les workflows

Comment survient une injection de script GitHub Actions et comment l’éviter ?

Impact élevé si privilégié CWE-78 Sécurité des workflows CI/CD
En clair

Un workflow peut transformer une donnée en syntaxe shell en insérant un titre d’issue ou un champ de pull request dans le script avant le démarrage de Bash. Comme une imprimante de badges qui prend un nom pour une consigne. Passez la valeur par une variable d’environnement et mettez son expansion entre guillemets lorsqu’elle sert de donnée : Bash la reçoit séparément du script. L’impact dépend toujours du jeton, des secrets, du runner et de l’événement.

Contexte non fiableChamps d’événement GitHub : titres, corps, noms de branches, libellés et messages de commit. Traitez-les comme des chaînes contrôlées par un tiers.
Substitution d’expressionGitHub évalue ${{ ... }} avant le lancement du shell. Dans un bloc run:, l’entrée peut ainsi s’insérer dans le code du script.
Transmission par environnementAffecter la valeur à env: la transmet comme donnée de processus. Mettez l’expansion shell entre guillemets et évitez eval et les scripts construits dynamiquement.
Frontière de privilègesL’événement et le workflow fixent les permissions du jeton, les secrets disponibles et l’accès au runner. L’impact dépend des ressources accessibles au job.

Cause racine : les données sont insérées avant l’analyse shell

GitHub développe les expressions avant de lancer le shell d’une étape run. Si un titre non fiable est interpolé dans le script, ses caractères peuvent modifier le programme shell au lieu de rester des données. Une valeur transmise par env: reste séparée du texte du script ; entre guillemets, son expansion reste un argument pour les commandes ordinaires. Cela ne sécurise pas un usage ultérieur avec eval, un autre interpréteur ou un outil qui traite les arguments comme du code. GitHub considère les champs d’événement non fiables comme contrôlables par un attaquant.

.github/workflows/triage.yml (Interpolation risquée)Risqué
# RISQUÉ : les données de l’événement entrent dans le script shell généré
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 (Donnée séparée)Protégé
# Plus sûr : transmettre la valeur de l’événement par l’environnement
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"

Chemin d’attaque résumé

Step 1

Un utilisateur contrôle un champ d’événement

Par exemple, le titre d’une issue publique, d’une pull request ou un nom de branche devient une entrée du workflow.

Step 2

Le workflow l’insère dans le code shell

Une expression directe ${{ github.event... }} dans run: est évaluée avant l’analyse du script par Bash.

Step 3

Le shell interprète le programme modifié

Si la valeur modifie la syntaxe, des commandes peuvent s’exécuter sous l’identité du processus du job. Si elle reste une donnée utilisée comme argument entre guillemets, cette injection ne se produit pas.

Step 4

Les permissions du job définissent l’impact

Un job compromis n’accède qu’aux ressources exposées à cette exécution : permissions effectives de GITHUB_TOKEN, secrets fournis et ressources du runner. Examinez attentivement les événements d’issue et pull_request_target.

Liste de durcissement

Sources primaires

Explorer les sujets de sécurité et analyses associés :Répertoire complet de sécurité →
À voir aussi Cosign GHSA-fx35-mq7g-6g98 : Bundle contourné Dependency Confusion : risques et défenses Exposition du socket Docker : risques et défenses