Attack Chokepoints 2 invariant stages
Each stage is an invariant condition the attacker must satisfy, regardless of tool, variant, or threat actor. Detection at any stage breaks the chain.
1 DRS Enrollment with Non-Native User Agent ▶
- Kit has already captured victim's session via AiTM relay (Tier 1 success)
- Kit holds a Microsoft Authentication Broker refresh token for the victim
- Victim tenant allows device registration (not restricted to Entra ID joined devices only)
- Kit has network access to enterpriseregistration.windows.net (DRS endpoint)
- Entra ID AuditLogs (AuditLogs table in Sentinel; filter ActivityDisplayName "Add registered users to device")
- Entra ID Sign-in Logs (cross-reference UPN for preceding AiTM sign-in within 5-15 min)
2 PRT Persistence Post-Revocation ▶
- Microsoft Graph Activity Logs (incomingTokenType field)
- Entra ID NonInteractiveUserSignInLogs
Variations 1 variant tracked
Tools and methods that exploit this chokepoint. The list grows. The chokepoint doesn't change.
Tycoon 2FA - Device PRT Enrollment Chain 2023-Q3 Active ▶
# Step 1: Resource-swap MAB refresh token for DRS access token
# User-Agent: axios/1.15.2
POST https://login.microsoftonline.com/common/oauth2/token
grant_type=refresh_token
refresh_token=<stolen_refresh_token>
resource=urn:ms-drs:enterpriseregistration.windows.net
client_id=29d9ed98-a469-4536-ade2-f981bc1d605e
# Step 2: Register synthetic device
# User-Agent: axios/1.15.2
POST https://enterpriseregistration.windows.net/EnrollmentServer/device
{"CertificateRequest":{"Type":"pkcs10","Data":"<base64_PKCS10_CSR>"},
"DeviceDisplayName":"<fabricated>","OSVersion":"<fabricated>"}
# Step 3-4: JWT bearer grant → obtain PRT
POST https://login.microsoftonline.com/common/oauth2/token
grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
assertion=<RS256_JWT_signed_with_device_key>
# Step 5: Ongoing - sign PRT assertions with HMAC-SHA256 session key
- Entra ID AuditLogs: operationName "Add registered users to device" resultType "0" userAgent "axios/1.15.2"
- Entra ID Device objects: fabricated DeviceDisplayName, OSVersion on registeredDevices/ownedDevices
- Graph Activity Logs: incomingTokenType "primaryRefreshToken" from cloud-VPS ASN post-revokeSignInSessions
Detection Strategy
Rules organized by the chokepoint stage they detect. Each stage has one or more rules at different maturity levels.
Device registration with user agent NOT in the native Entra ID join client se...
Hunt
▶
operationName == "Add registered users to device" AND resultType == "0" AND userAgent NOT contains_any [Dsreg, DeviceRegistrationClient, Dalvik, Windows-AzureAD-Authentication-Provider]. Correlate with preceding AiTM sign-in from same UPN within 5-15 minutes (Node.js UA on OfficeHome/Auth Broker).
title: Entra ID Device Registration with Non-Native User Agent (AiTM PRT Persistence)
id: f6a7b8c9-d0e1-4f2a-3b4c-5d6e7f8a9b0c
status: experimental
description: >
Detects Entra ID device registration audit events where the initiating user agent
is not a native OS registration client. Tycoon 2FA's kit performs DRS
EnrollmentServer/device enrollment using axios/1.15.2, not the expected OS agents
(Dsreg, DeviceRegistrationClient, Dalvik, Windows-AzureAD-Authentication-Provider).
A registered device from a non-native UA obtains a PRT that survives
revokeSignInSessions, enabling persistent M365 access after IR response.
VERIFY field names (UserAgent vs. InitiatedBy.app.displayName or AdditionalDetails)
against lab AuditLogs data before production deployment.
references:
- https://www.elastic.co/security-labs/tycoon-2fa-aitm-detection-engineering
author: '@iimp0ster'
date: 2026-05-27
tags:
- attack.persistence
- attack.t1098.005
- detection.maturity.hunt
logsource:
product: azure
service: auditlogs
detection:
selection:
OperationName: 'Add registered users to device'
ResultType: '0'
filter_native_ua:
# Native OS registration clients — these are expected
UserAgent|contains:
- 'Dsreg'
- 'DeviceRegistrationClient'
- 'Dalvik'
- 'Windows-AzureAD-Authentication-Provider'
filter_known_automation:
# PENDING: Add documented automation service accounts or Intune pipeline identities
# InitiatedBy|contains:
# - 'svc-intune@tenant.onmicrosoft.com'
InitiatedBy: '' # placeholder — remove and populate before production
condition: selection and not filter_native_ua and not filter_known_automation
falsepositives:
- Third-party MDM solutions using custom HTTP clients for device enrollment
- Custom Intune enrollment wrappers with non-standard UA strings
- Vendor tooling not yet in the native UA filter
level: high
Device registration with non-native UA correlated with preceding AiTM sign-in...
Analyst
▶
operationName == "Add registered users to device" AND resultType == "0" AND userAgent NOT contains_any [Dsreg, DeviceRegistrationClient, Dalvik] AND same UPN has preceding sign-in with Node.js UA within 15 minutes. Correlated alert: high confidence AiTM device-PRT persistence chain.
title: Entra ID AiTM Device PRT Enrollment — Non-Native UA Correlated with Preceding AiTM Sign-In
id: a7b8c9d0-e1f2-4a3b-4c5d-6e7f8a9b0c1d
status: experimental
description: >
Detects a synthetic Entra ID device registration (non-native UA) within 15 minutes
of a preceding AiTM kit relay sign-in (Node.js UA on OfficeHome/Auth Broker) for the
same UPN. This correlation confirms the Tycoon 2FA persistence chain: kit relay steals
session token → kit immediately enrolls synthetic device using DRS API with axios UA →
device-bound PRT acquired → persists through revokeSignInSessions. IR teams MUST delete
registered devices BEFORE revoking sessions; skipping device deletion leaves the PRT
active and allows immediate re-authentication.
NOTE: This rule requires correlation across two log sources (AuditLogs + SigninLogs) --
implement as KQL join or SIEM correlation rule.
KQL join (Sentinel):
let AiTMSignIn = SigninLogs
| where ResultType == "0"
and UserAgent has_any ("node","axios","undici","node-fetch")
and AppDisplayName has_any ("OfficeHome","Microsoft Authentication Broker")
| project AiTMTime = TimeGenerated, UserPrincipalName;
AuditLogs
| where OperationName == "Add registered users to device" and ResultType == "0"
| where not(UserAgent has_any ("Dsreg","DeviceRegistrationClient","Dalvik"))
| extend UPN = tostring(InitiatedBy.user.userPrincipalName)
| join kind=inner AiTMSignIn on $left.UPN == $right.UserPrincipalName
| where TimeGenerated between (AiTMTime .. AiTMTime + 15m)
references:
- https://www.elastic.co/security-labs/tycoon-2fa-aitm-detection-engineering
author: '@iimp0ster'
date: 2026-05-27
tags:
- attack.persistence
- attack.t1098.005
- detection.maturity.analyst
logsource:
product: azure
service: auditlogs
detection:
# This rule requires correlation across AuditLogs and SigninLogs — use the KQL join above.
# Sigma stub fires on device registration with non-native UA; full correlation requires SIEM.
selection_device_registration:
OperationName: 'Add registered users to device'
ResultType: '0'
filter_native_ua:
UserAgent|contains:
- 'Dsreg'
- 'DeviceRegistrationClient'
- 'Dalvik'
- 'Windows-AzureAD-Authentication-Provider'
# CORRELATION: same UPN in SigninLogs with Node.js UA within 900s preceding -- KQL required
condition: selection_device_registration and not filter_native_ua
falsepositives:
- Automated device enrollment workflows triggered by sign-in event (document and exclude)
- Split-session scenarios where Node.js tooling and device enrollment coincidentally overlap
level: high