Une compromission Azure décortiquée (cas fictif)
Une compromission Azure fictive, analysée de bout en bout : secret fuité, rôle auto-attribué, Run Command, jeton d'identité managée volé, Key Vault et blobs.
En bref. Ce cas est fictif. Kestrel Freight n'existe pas ; son tenant, ses identités et ses adresses IP (plages de documentation RFC 5737) sont inventés, et les logs ont été générés dans les vrais formats d'export Azure pour le bouton Essayer un exemple de l'analyseur. En 41 minutes, un dimanche soir, un principal de service CI/CD dont le secret a fuité s'attribue Contributor, exécute un script sur une VM, rejoue le jeton de l'identité managée de la VM, s'ajoute à la stratégie d'accès d'un Key Vault, lit les 12 secrets, génère un SAS de compte, télécharge 330 sauvegardes de base de données (environ 48,6 Go), puis supprime le paramètre de diagnostic du coffre et l'export de l'Activity Log. L'analyseur rend le verdict Compromis avec 11 constats de sévérité haute.
Les vrais incidents sont désordonnés, partiels et confidentiels. Un incident synthétique me permet de montrer chaque étape avec les preuves sous les yeux. Chargez-le vous-même : ouvrez l'analyseur Azure Forensics et cliquez sur Essayer un exemple. La page affiche un bandeau rappelant qu'il s'agit de l'exemple fictif.
Le contexte
Kestrel Freight (fictive) fait tourner un portail web sur vm-app-01 dans rg-prod-app. La VM dispose d'une identité managée affectée par le système qui lit toutes les heures sa chaîne de connexion à la base dans le Key Vault kv-kestrel-prod, et sa configuration dans le compte de stockage stkestrelbackups, où sont aussi écrites les sauvegardes nocturnes de la base. Un principal de service CI/CD, sp-gh-deploy, déploie le portail chaque matin depuis deux IP de runners. Surprivilégié par commodité, il détient User Access Administrator.
L'export couvre du 2026-09-07 au 2026-09-14 : une semaine d'activité normale (la période d'apprentissage), puis l'incident. Il contient :
| Fichier / dossier | Source | Format |
|---|---|---|
insights-activity-logs/…/PT1H.json | Activity Log | Export de stockage, JSON Lines |
activity-log-az-cli.json | Activity Log, jour de l'incident | az monitor activity-log list (schéma REST) |
insights-logs-auditevent/… | Key Vault AuditEvent | JSON Lines |
insights-logs-storageread/…, …storagewrite/… | Logs blob | JSON Lines |
insights-logs-flowlogflowevent/… | Flow logs VNet | {"records": […]} |
insights-logs-networksecuritygroupflowevent/… | Flow logs NSG v2 | {"records": […]} |
defender-alerts.json | Defender for Cloud | REST {"value": […]} |
L'export de stockage et l'export CLI se recouvrent sur le jour de l'incident : l'analyseur fusionne 11 événements en double.
D'abord la couverture
Les cinq sources sont présentes. L'Activity Log compte 99 événements, Key Vault 186, Storage 509, les flux 88, Defender 1. La période d'apprentissage est suffisante, donc les règles de première apparition s'exécutent. Bonne nouvelle : un verdict sur ces données a du sens pour le plan de contrôle comme pour le plan de données.
La chronologie, minute par minute (UTC, 2026-09-14)
| Heure | Événement | Source | Constat(s) |
|---|---|---|---|
| 02:14:05 | sp-gh-deploy appelle ARM depuis 203.0.113.66, jamais vue auparavant, et s'attribue Contributor sur l'abonnement à lui-même | Activity Log | AZ-ID-001, AZ-RBAC-002, AZ-RBAC-003 |
| 02:19:40 | runCommand/action sur vm-app-01, même principal, même IP | Activity Log | AZ-VM-001 |
| 02:20:05 | La VM 10.10.1.4 se connecte à 203.0.113.66:80 (812 o envoyés, 46 338 o reçus), puis :443 | Flow log VNet | AZ-NET-004 |
| 02:22:10 | L'identité managée de la VM lit db-conn-string depuis 203.0.113.66 avec curl/8.5.0 | Key Vault | AZ-ID-002 |
| 02:23:30 | Le principal écrit la stratégie d'accès du coffre (secrets get / list pour lui-même) | Activity Log + Key Vault | AZ-KV-003 |
| 02:24:10–02:25:14 | SecretList, puis 12 SecretGet distincts par le principal, SDK Python | Key Vault | AZ-KV-001, AZ-KV-002 |
| 02:25:03 | Alerte Defender for Cloud sur la VM (heure de génération ; l'heure de début de l'alerte est 02:21) | Defender | AZ-DEF-001 |
| 02:31:02 | listAccountSas/action sur stkestrelbackups : blob, lecture + listage, valide 7 jours | Activity Log | AZ-ST-001 |
| 02:33:00–02:51:29 | 330 blobs de sauvegarde distincts téléchargés avec ce SAS depuis 203.0.113.66, user agent AzCopy, ~48,6 Go | Storage | AZ-ST-005 |
| 02:55:12 | Paramètre de diagnostic kv-audit-to-storage du Key Vault supprimé | Activity Log | AZ-DE-001 |
| 02:55:40 | Paramètre de diagnostic de l'abonnement export-activity-to-storage supprimé | Activity Log (export CLI uniquement) | AZ-DE-002 |
Quelques points méritent l'attention, car ils se généralisent aux vrais dossiers.
Le premier événement est l'auto-attribution, pas la connexion. L'Activity Log commence là où l'attaquant touche ARM pour la première fois. Comment le secret du principal a fuité relève d'Entra ID (connexions des principaux de service, changements d'identifiants), à analyser avec m365forensics.com.
Le jeton de l'identité managée apparaît deux minutes après le Run Command. Le script a interrogé l'Instance Metadata Service et envoyé le jeton à l'extérieur. La seule trace visible est le même object ID, rattaché à vm-app-01 par sa claim xms_mirid, qui appelle Key Vault depuis l'IP de l'attaquant au lieu du 10.10.1.4 de la VM. C'est AZ-ID-002, expliqué dans l'abus d'identités managées.
L'élévation par stratégie d'accès ne demande aucun rôle supplémentaire. Contributor suffisait pour ajouter une stratégie d'accès. Voir les logs d'audit Key Vault.
Le SAS est le principal du téléchargement. Les 330 requêtes GetBlob ne portent ni utilisateur ni application, seulement l'authentification SAS et le hash de la signature SAS. L'analyseur les présente comme un seul principal sas:… avec une IP et un total d'octets (exfiltration Storage).
L'export de l'Activity Log meurt à 02:55:40, pas l'Activity Log. L'Activity Log exporté vers le stockage ne contient plus rien après 02:55:40. La suppression de l'export lui-même n'est visible que parce que l'enquêteur a aussi lancé az monitor activity-log list, qui lit la copie de 90 jours d'Azure (évasion de défense).
Une réserve honnête sur l'exemple : son événement Run Command inclut le corps de la requête avec le script. Les vraies entrées de l'Activity Log pour Run Command n'incluent généralement pas le contenu du script, comme le montrent les recherches de Mandiant ; il faudrait le récupérer sur la VM (attaques Run Command).
Le verdict
Onze constats de sévérité haute (AZ-RBAC-002, AZ-RBAC-003, AZ-VM-001, AZ-NET-004, AZ-ID-002, AZ-KV-001, AZ-KV-002, AZ-ST-001, AZ-ST-005, AZ-DE-001, AZ-DE-002) et trois moyens (AZ-ID-001, AZ-KV-003, AZ-DEF-001) : Compromis. Il y a aussi un constat faible issu de la semaine d'apprentissage : l'administratrice de la plateforme qui liste les clés du compte de stockage le 2026-09-09 (AZ-ST-002). C'est légitime, et le verdict l'ignore : un rappel utile que les constats faibles sont du contexte, pas des accusations.
La vue par entités
Pivoter sur 203.0.113.66 dans l'onglet Entités rassemble tous les événements de cette adresse : les appels ARM du principal, la lecture Key Vault de l'identité managée, les lectures de secrets du principal et les téléchargements par SAS. La ligne de l'IP porte aussi les totaux de flux issus des flow logs VNet. Ce seul filtre, c'est l'incident.
Pivoter sur l'object ID du principal montre le contraste avec sa référence : une semaine de déploiements quotidiens à 10:00 depuis deux IP de runners, puis une rafale de 41 minutes depuis une nouvelle IP, faisant des choses qu'il n'avait jamais faites.
La checklist de remédiation générée
L'onglet Remédiation ordonne les étapes à partir des constats :
- Supprimer les attributions de rôles créées pendant l'incident ; revoir qui détient Owner / User Access Administrator.
- Faire tourner les identifiants du principal et révoquer les sessions ; enquêter côté identité dans Entra ID.
- Isoler
vm-app-01, faire des snapshots de ses disques, la reconstruire ; revoir les extensions et l'historique Run Command. - Bloquer 203.0.113.66 et le rechercher ailleurs.
- Faire tourner les 12 secrets lus dans
kv-kestrel-prod; passer le coffre en Azure RBAC ; supprimer la stratégie d'accès malveillante. - Renouveler les deux clés du compte de stockage (ce qui tue le SAS) ; établir précisément quelles sauvegardes sont sorties, pour la notification.
- Rétablir les paramètres de diagnostic supprimés et les protéger avec Azure Policy.
- Examiner l'alerte Defender.
Ce que ce cas ne montre pas
- L'accès initial. La fuite du secret est hors de ces logs.
- Ce que le script a fait sur la VM, au-delà des flux réseau et de l'usage du jeton. Il faut le disque.
- Tout ce qui suit 02:55:12 dans Key Vault. La journalisation du coffre disparaît à ce moment ; si l'attaquant était revenu à 03:10, le log Key Vault ne le dirait pas.
Pour le processus autour d'un tel cas, partez du guide de réponse à incident ; pour l'outil lui-même, du guide pas à pas.