Enquête sur une attaque supply chain GitHub : cas pratique
Attaque supply chain GitHub fictive analysée de bout en bout : PAT volé, 38 dépôts clonés, clés AWS affichées par un workflow, protection de branche retirée.
En bref. Ce cas pratique s'appuie sur l'incident fictif du bouton « Essayer un exemple » de l'outil (organisation northwind-labs ; personnes, dépôts, jetons et adresses IP sont tous inventés). Le jeton d'accès personnel (PAT) classique d'un développeur est rejoué depuis un VPS aux Pays-Bas à 01:58 UTC. Il clone 38 dépôts privés en 18 minutes environ, pousse sur une nouvelle branche un workflow qui affiche les clés de déploiement AWS encodées deux fois en base64, ajoute une clé de déploiement en écriture et supprime la protection de branche sur main. L'analyseur rend le verdict Compromis avec 10 constats. Voici comment les lire et dans quel ordre agir.
Scénario fictif. Tout le contenu de cet article provient de
scripts/make-samples.py, qui génère l'exemple servi par le bouton « Essayer un exemple » de l'outil. Les adresses IP proviennent des plages réservées à la documentation (RFC 5737), et la paire de clés AWS est l'exemple officiel de la documentation AWS. Toute ressemblance avec une organisation réelle serait fortuite.
Le contexte
northwind-labs est une petite organisation d'ingénierie : une propriétaire (c-okafor) et trois développeurs (m-hernandez, j-lindqvist, a-nowak) qui travaillent surtout depuis la France et la Suède, aux heures de bureau. Les preuves sont les trois fichiers qu'un enquêteur collecterait réellement, chacun dans son format d'export réel :
| Fichier | Source | Contenu |
|---|---|---|
northwind-labs-audit-log.json | Journal d'audit de l'organisation, export JSON depuis l'interface | Modifications des membres, exécutions de workflows, secrets, clés, protection de branche |
export-northwind-labs-1789360000.json.gz | Export des événements Git | git.clone / git.fetch / git.push avec les métadonnées des jetons |
northwind-labs-actions-run-4412.zip | « Download log archive » d'une exécution | Logs des étapes de l'exécution de workflow suspecte |
La base de référence couvre trois semaines (du 2026-08-24 au 2026-09-13). C'est important, car les règles « nouveau pays » et « nouvelle adresse IP » ont besoin d'un historique pour comparer.
Étape 1 : le verdict
Déposez les trois fichiers (ou cliquez sur Essayer un exemple). Le bandeau indique Compromis. Sous Pourquoi, quatre constats l'ont déterminé : actions-secret-exfil (critique), ainsi que token-new-country, git-clone-burst et branch-protection-removed (élevée). Les notes de couverture sont vides. Adresses IP, événements Git et métadonnées des jetons sont tous présents : aucune règle n'était aveugle.
La liste complète, telle que l'analyseur la présente :
| Gravité | Règle | Quand (UTC, le 2026-09-14 sauf mention contraire) | Quoi |
|---|---|---|---|
| critique | actions-secret-exfil | 02:40:08 | Le script de l'étape fait passer les clés AWS deux fois dans base64. Rebond suivant : AWS |
| élevée | token-new-country | 01:58:12 → 03:06:30 | 43 événements depuis les Pays-Bas pour un jeton jamais vu ailleurs qu'en France |
| élevée | git-clone-burst | 02:14:00 → 02:31:53 | 38 dépôts distincts clonés depuis 203.0.113.66 |
| élevée | branch-protection-removed | 03:05:44 | main sur payments-api |
| moyenne | actions-encoded-output | 02:40:08 | Longue chaîne base64 affichée dans la même étape |
| moyenne | deploy-key-added | 02:52:03 | Clé backup-sync sur infra-terraform, accès en écriture |
| faible | token-new-ip | 01:58:12 → 03:06:30 | Les mêmes 43 événements, nouvelle adresse IP |
| faible | workflow-new-branch | 02:39:40 | Le workflow CI s'exécute pour la première fois sur ci/cache-fix |
| faible | actions-secret-created | 2026-09-08 | a-nowak a créé les secrets AWS, légitime |
| faible | webhook-created | 2026-09-03 | a-nowak a créé un webhook de CI, légitime |
Les deux derniers constituent le bruit normal de toute organisation réelle : des tâches d'administration courantes que les règles font remonter parce qu'elles pourraient compter. Ils ne partagent ni acteur, ni jeton, ni adresse IP avec le reste : vous pouvez les écarter.
Étape 2 : quel identifiant ?
Ouvrez token-new-country. Ses entités sont l'acteur m-hernandez, un hashed_token et l'adresse IP 203.0.113.66. Pivotez sur le jeton (Entités → Tokens → Voir les événements). Il s'agit d'un Personal access token (classic) avec les scopes repo, workflow, read:org. Pendant trois semaines, il a été utilisé depuis 198.51.100.23 (France) avec le user agent git/2.39.5 (Apple Git-154), en journée. À partir de 01:58 UTC le 2026-09-14, il apparaît depuis 203.0.113.66 (Pays-Bas) avec git/2.43.0, puis avec curl/8.5.0.
Même personne, même jeton, autre machine, autre pays, en pleine nuit : le jeton a été rejoué. Le scope workflow explique comment l'attaquant a pu pousser un fichier de workflow. Dans le scénario, la source est un infostealer sur le poste du développeur. Les logs GitHub ne peuvent pas montrer cette partie, c'est pourquoi le playbook sur les jetons fuités insiste sur la vérification du poste.
Étape 3 : qu'a-t-il emporté ?
git-clone-burst compte 38 dépôts distincts. Dans les événements Git, les clonages s'étalent de 02:14:00 à 02:31:53, soit un toutes les 29 secondes environ. Un rythme aussi régulier trahit un script. m-hernandez travaille normalement sur quatre dépôts (payments-api, billing-worker, ledger, web-app). La rafale inclut infra-terraform, secrets-rotation, kyc-service et terraform-modules, des dépôts sur lesquels il n'a jamais poussé.
Conclusion pour le rapport : le code source et l'historique complet de 38 dépôts sont entre les mains de l'attaquant. L'action de remédiation « Considérez le code cloné comme divulgué » en découle : analysez ces historiques à la recherche de secrets et renouvelez ceux que vous trouvez. L'article sur l'exfiltration de dépôts explique comment délimiter ce périmètre.
Étape 4 : qu'a-t-il modifié ?
Lisez la chronologie avec Constats uniquement :
- 02:39:21
git.pushverspayments-apidepuis le VPS. - 02:39:40
workflows.created_workflow_run: workflowCI, événementpush, brancheci/cache-fix. C'est la première exécution sur cette branche, doncworkflow-new-branchse déclenche. - 02:40:08 Dans l'archive de logs de l'exécution
4412, l'étape Restore build cache exécuteecho "$AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY" | base64 -w0 | base64 -w0et affiche une longue chaîne. Le masquage ne s'est pas appliqué, car la valeur était encodée. Décodée deux fois, elle donne la paire de clés (ici, l'exemple de la documentation AWS).actions-secret-exfilse déclenche en critique avec le rebond suivant AWS, etactions-encoded-outputse déclenche sur la chaîne. - 02:52:03
public_key.createsurinfra-terraform: titrebackup-sync,read_only: false, user agentcurl/8.5.0. Une clé de déploiement en écriture est une persistance qui survit à la révocation du PAT. - 03:05:44
protected_branch.destroysurpayments-api/main. La règle exigeait deux reviews approbatrices. - 03:06:30
git.pushverspayments-api, une minute après la levée de la protection. - 07:48:10
protected_branch.createparc-okafordepuis le bureau : la propriétaire s'en est aperçue et a rétabli la règle.
L'étape 6 est celle qui doit le plus vous inquiéter. Quelque chose a été poussé sur main sans review. Le journal d'audit ne contient pas le diff : le commit doit donc être examiné dans le dépôt (voir altération de la protection de branche).
Étape 5 : la remédiation, dans l'ordre de l'outil
L'onglet Remédiation classe la checklist par urgence :
- Préservez les preuves : réexportez le journal d'audit et les événements Git, et conservez l'archive de logs de l'exécution, avant que quelqu'un ne supprime les logs de l'exécution 4412.
- Renouvelez tous les secrets Actions accessibles : tous les secrets lisibles par les workflows de
payments-api, pas seulement les deux secrets AWS. - Renouvelez les identifiants cloud et investiguez le cloud : désactivez la clé AWS et fouillez CloudTrail à partir de 02:40 UTC. Voir le pivot cloud et AWS Forensics.
- Passez en revue les modifications et exécutions de workflows : comparez
.github/workflowssurci/cache-fix, supprimez la branche et exigez une review CODEOWNERS pour les fichiers de workflow. - Révoquez le jeton et réinitialisez le compte, après avoir réinstallé le poste de travail.
- Considérez le code cloné comme divulgué : analysez l'historique des 38 dépôts.
- Rétablissez la protection de branche et vérifiez ce qui est passé : le push de 03:06.
- Supprimez la persistance : supprimez la clé de déploiement
backup-syncet recherchez tout autre élément créé depuis203.0.113.66.
Ce que cet exemple ne montre pas
Les incidents réels sont plus désordonnés. Il n'y a ici ni prise de contrôle par un propriétaire, ni runner auto-hébergé, ni suppression du flux d'audit. L'attaquant n'a pas non plus étalé son activité sur plusieurs jours pour rester sous les seuils. Si une règle ne s'est pas déclenchée sur vos données, vérifiez les notes de couverture et ce que le journal d'audit n'enregistre pas avant de conclure.