Service Hacktion - Notes attachées au balado S01E11 - Panorama de la sécurité de Github Action

Saison Épisode
1 11

Notes#

Vocabulaire#

  • IC : intégration continue (CI : continuous integration en anglais)
  • DC : déploiement continue (CD : continuous deployment en anglais)
  • Github Action : nom de l'outil de IC/DC intégré de Github
  • ECD : exécution de code à distance (RCE : remote code execution en anglais)
  • fusiodemande : demande de fusion d'un changement de code dans un dépôt git (merge request en anglais)
  • tube ou bitoduc : conduits transportant des bits (pipeline en anglais)

Partie 1 - Aperçu#

  • Introduction
  • Contexte GitHub
    • Contextes intéressant pour un attaquant : env, secrets, github, steps, needs
    • Exemple d'ECD : ${{ github.event.issue.title }} et $(id) dans le titre d'un ticket
  • Secrets Github
  • Permissions des flux de travaux
    • github.token ou secrets.GITHUB_TOKEN : jeton d'authentification spécifique à chaque tâche
    • Par défaut, le jeton à accès en lecture seule au dépôt
    • Avant 2022, le jeton avait aussi le droit d'écriture par défaut
    • Le changement n'est pas forcé, les organisations créées avant cette date ont toujours le droit d'écrite par défaut
    • Peut être défini de manière granulaire
  • Nouveaux contributeurs
    • Par défaut, les flux de travail déclenchés par des fusiodemandes des nouveaux contributeurs externes à des dépôts publics demandent une approbation pour s'exécuter automatiquement
    • Le flux de travail sera mis en pause en attendant
    • Facilement contournable : faire 1ère contribution anodine et une 2ième malveillante
  • Déclenchements des flux de travail
    • push - peu utile pour l'attaquant
    • pull_request - pour cet évènement GITHUB_TOKEN est restreint : il ne peut pas avoir de droit en écriture, sinon tout le monde pourrait modifier le code du projet, et ne peut pas lire les secrets
    • pull_request_target - lors d'activité sur une fusiodemande, ex : quand la branche a été mise à jour, à accès en écriture et aux secrets, peut être configuré de manière granulaire sur les types d'évènements
    • workflow_run - quand un flux de travail est demandé ou en cours ou complété, permet de chainer plusieurs flux de travail, n'hérite pas des droits, le flux fils (celui avec workflow_run) est privilégié (écriture + accès aux secrets)
      • action/checkout@v2 + ref: ${{ github.event.workflow_run.head_sha }} : se base sur le commit qui a déclenché le flux de travail, peut exécuter le code de l'attaquant, ex : dans un des étapes suivantes est exécutée la commande npm run script-perso et que l'attaquant a modifié package.json pour contenir sa commande malveillante dans scripts.script-perso.
      • Téléchargement d'un artefact et fait quelque chose avec, il suffit alors d'écrire par dessus

Partie 2 - Exemples de cas réels#

  • Injection d'expressions - Éléments contrôlables par un attaquant
    • github.event.issue.{title,body}, github.event.pull_request.{title,body}, github.event.{comment,review}.body, github.event.commits.*.message, github.event.commits.*.author.{email,name}, env.*, etc.
    • Apache Superset : évènement issue_comment + github.event.comment.body dans un run
    • AutoGPT : évènement pull_request_target sur plusieurs branches + github.event.pull_request.head.ref concaténé dans un script bash, ex. de charge utile ";{echo,aWQK}|{base64,-d}|{bash,-i};echo" en nom de branche puis fusiodemande
    • Microsoft Generative AI for Beginners : évènement pull_request_target + actions/checkout@v3 avec ref: ${{ github.event.pull_request.head.sha }} dont on peut abuser avec un fichier Markdown spécifiquement conçu
    • ant-design : évènement workflow_run + actions/checkout@v4 + dawidd6/action-download-artifact@v2 et un fichier JS est exécuter, il suffit de créer un flux de travail avec le même nom et d'utiliser actions/upload-artifact@v3
  • Bascules (git) dangereuses
    • Cypress : évènement pull_request_target + actions/checkout@v3 avec ref: ${{ github.event.pull_request.head.ref }} puis npm install, il suffit d'éditer package.json
    • AutoGPT : évènement pull_request_target + actions/checkout@v3 avec ref: ${{ github.event.pull_request.head.ref }} puis pip install -r requirements.txt, il suffit d'éditer requirements.txt
    • excalidraw : évènement issue_comment + … + actions/checkout@v2 où est récupéré l'identifiant de la fusiodemande puis utilisation de yarn, il suffit de créer un fichier .yarnrc.yml
    • Apache Doris ou FreeRDP ou Angular : évènement pull_request_target + actions/checkout@v3 avec ref: ${{ github.event.pull_request.head.sha }} et applique un correctif sur actions/action-sh-checker

Partie 3#

  • Détournement de dépôt (repository hijacking en anglais)
    • Je créé des mots-valises : détournepôt ou dépônement (repojacking en anglais)
    • Quand un flux de travail fait référence à une action sur une organisation ou un utilisateur GitHub inexistant (similaire aux prises de contrôle d'un sous-domaine)
    • Il suffit de réclamer l'organisation et de créer un dépôt pour l'action mentionnée pour avoir une ECD dans le flux de travail
    • Pour se protéger contre le détournement de dépôt, GitHub utilise un mécanisme de sécurité qui interdit l'enregistrement de dépôts ayant déjà existés avec plus de 100 clones au cours de la semaine précédant le changement de nom ou la suppression du compte du propriétaire
    • Cela est arrivé sur un dépôt Azure
  • Écriture dangereuse
    • Des variables d'environnement crées par défaut
    • Les variables d'environnement sont partagées entre les étapes
    • On peut ECD avec LD_PRELOAD ou NODE_OPTIONS si on peut contrôler cette variable dans une autre étape
      • NODE_OPTIONS="--experiment --experimental-loader=data:text/javascript,console.log('injection');"
    • Exemple d'un cas un peu plus sofistiqué pour swagger-editor
    • NODE_OPTIONS mis sur liste noire dans les versions récentes de GitHub runner
  • Commande de flux de travail
    • Commande pour interagir directement avec la machine
    • Avant 2020 il était facile d'injecter set-env pour ECD
    • Si le dev. active ACTIONS_ALLOW_UNSECURE_COMMANDS, on peut utiliser set-env
    • Il y a toujours set-output, déprécié, vuln. dans un dépôt Firebase
      • Test si pr_number.txt contient bien un nombre
      • La valeur du fichier est stocké dans echo "::set-output name=pr_number::$pr_number"
      • Puis utilisé dans une autre étape ${{ steps.unzip.outputs.pr_number }}
      • La commande unzip va afficher le nom du fichier dans STDOUT
      • Avec un nom de fichier malveillant dans le Zip on peut donc contrôler la valeur de pr_number qui sera executé
      • Exemple de nom de fichier dans le Zip : steps/Hello ##[set-output name=pr_number;]'end'}); console.log('pwn') ; console.log({console
      • pr_number = 'end'}); console.log('pwn') ; console.log({console

Références#

Synacktiv

Legit Security

Partager