Du contexte GitHub à l’injection de commandes sur le Runner

GitHub Actions est devenu un élément central des pipelines CI/CD (Continuous Integration/Continuous Developement) modernes. Cependant, une mauvaise manipulation des entrées utilisateur dans les fichiers de workflow peut mener à des vulnérabilités critiques d’injection de commandes. Cet article explore comment des données provenant du contexte GitHub peuvent être exploitées pour exécuter des commandes arbitraires sur le runner.

Comprendre le contexte GitHub

Les workflows GitHub Actions utilisent des expressions pour accéder à des données sur l’exécution du workflow, les secrets, les variables d’environnement et le contexte de l’événement qui a déclenché l’action. Par exemple, ${{ github.event.issue.title }} permet d’accéder au titre d’une « issue » ou ${{ github.event.pull_request.head.ref }} au nom de la branche d’une pull request.

Il existe deux types de runners GitHub :

  • Runners publics : Ces runners hébergés sur GitHub sont fournis gratuitement avec GitHub Actions. Partagés entre différents utilisateurs et projets, ils sont donc moins sécurisés pour les opérations sensibles.
  • Runners privés : Ces runners sont auto-hébergés et gérés par les utilisateurs ou les organisations qui en ont la responsabilité. Ils offrent un meilleur contrôle de la sécurité et de l’environnement, mais nécessitent une maintenance.

Le problème : L’interpolation directe

La vulnérabilité survient lorsqu’un développeur utilise ces expressions directement dans un script shell (run:) sans nettoyage préalable. Comme ces données sont souvent contrôlées par l’utilisateur (n’importe qui peut ouvrir une issue ou créer une branche), un attaquant peut y injecter des caractères spéciaux de shell.

Exemple de workflow vulnérable

Considérons ce workflow déclenché lors de l’ouverture d’une issue :

on:
  issues:
    types: [opened]

jobs:
  print_issue_title:
    runs-on: ubuntu-latest
    steps:
      - name: Print issue title
        run: |
          echo "L'issue est : ${{ github.event.issue.title }}"

L’exploitation

Un attaquant pourrait créer une issue avec le titre suivant : " ; whoami ; #

Lorsque le workflow s’exécutera, GitHub remplacera l’expression par le titre malveillant. La commande finale exécutée sur le runner sera : echo "L'issue est : " ; whoami ; #"

Le shell exécutera echo, puis la commande whoami, et ignorera le reste grâce au commentaire #. L’attaquant vient de réussir une injection de commande.

Exemple pratique

PARTIE 1 : J’ai dû créer un workflow GitHub avec le contexte GitHub. Le contexte GitHub GITHUB.EVENT.ISSUE.TITLE donne accès au titre du ticket qui a déclenché le workflow dans GitHub Actions. Ce titre permet de personnaliser les actions en fonction de celui du ticket.

name: Issue

on:
  issues:

jobs:
  hello:
    runs-on: ubuntu-latest
    steps:
      - run: echo "New issue: ${{ github.event.issue.title }}"

PARTIE 2 : comme le contexte GitHub vous permet d’exécuter la commande echo suivie du titre du problème, nous allons injecter une commande dans le titre du problème GitHub et envoyer des commandes directement à notre runner cible, comme dans ce cas avec la commande $(id;ls;whoami).

PARTIE 3 : Les workflows GitHub seront exécutés automatiquement puisqu’il s’agit d’un problème et lanceront notre commande d’injection mentionnée dans le titre de notre problème. Je vous laisse imaginer ce que vous pouvez faire d’autre avec ça…

PARTIE 4 : Et la commande a été exécutée sans problème dans le runner.

gotchaaaa

Impact de l’attaque

Une injection de commande réussie sur un runner permet à l’attaquant de :

  1. Voler des secrets : Accéder aux variables d’environnement et aux secrets du dépôt (via env ou en exfiltrant le fichier .git).
  2. Compromettre le code : Modifier le code source ou injecter des backdoors avant le déploiement.
  3. Accéder au GITHUB_TOKEN : Si le jeton possède des permissions d’écriture, l’attaquant peut modifier le dépôt ou pousser des tags malveillants.

Comment prévenir ces injections

1. Utiliser des variables d’environnement (Recommandé)

La méthode la plus sûre consiste à passer les données du contexte à une variable d’environnement. Les variables d’environnement sont traitées comme des données et non comme du code exécutable par le shell.

Version sécurisée :

- name: Print issue title safely
  env:
    TITLE: ${{ github.event.issue.title }}
  run: |
    echo "L'issue est : $TITLE"

2. Utiliser des Actions dédiées

Au lieu d’écrire des scripts shell personnalisés, utilisez des actions officielles ou certifiées qui gèrent correctement l’échappement des données.

3. Appliquer le principe du moindre privilège

Limitez les permissions du GITHUB_TOKEN dans votre fichier YAML pour restreindre les dommages potentiels en cas d’injection.

permissions:
  contents: read
  issues: read

Conclusion

La sécurité des workflows GitHub Actions ne doit pas être négligée. L’interpolation directe de données provenant d’utilisateurs externes dans des scripts shell est une erreur courante mais extrêmement dangereuse. En adoptant l’utilisation systématique des variables d’environnement, vous éliminez l’un des vecteurs d’attaque les plus fréquents dans les pipelines CI/CD.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *