Token Protection Switch with Different IP
Hypothesis
Threat Actors will use token theft as a means of initial access, privilege escalation and persistence. Combined with phishing as a means for initial access.
Token Protection Switch with Different IP
This analytic looks at authentications in both SigninLogs and AADNonInteractiveUserSignInLogs, where the initial authentication is bound to a device, and the 2nd is unbound, and comes from a different IP address. This is also restricted to the "Microsoft Azure CLI" application, and should hopefully catch context shifts for authentications to the app. It also looks at a time period of 3h for the authentications, where the two different authentications occur within 10 minutes of each other. These thresholds can easily be adjusted.
let TimeWindow = 10m;
SigninLogs
| where TimeGenerated > ago(3h)
| where AppDisplayName == "Microsoft Azure CLI"
| where ResultType == 0
| extend
DeviceDetailParsed = parse_json(DeviceDetail),
LocationDetailParsed = parse_json(LocationDetails),
TokenProtection = parse_json(TokenProtectionStatusDetails)
| extend
DeviceName = tostring(DeviceDetailParsed.displayName),
Location = tostring(LocationDetailParsed.countryOrRegion),
SessionStatus = tostring(TokenProtection.signInSessionStatus)
| where IncomingTokenType == "primaryRefreshToken" and SessionStatus == "bound"
| project
InteractiveTime = TimeGenerated,
UserPrincipalName,
DeviceName,
VictimIP = IPAddress,
VictimLocation = Location,
VictimASN = AutonomousSystemNumber
| join kind=inner (
AADNonInteractiveUserSignInLogs
| where TimeGenerated > ago(3h)
| where AppDisplayName == "Microsoft Azure CLI"
| extend
DeviceDetailParsed = parse_json(DeviceDetail),
LocationDetailParsed = parse_json(LocationDetails),
TokenProtection = parse_json(TokenProtectionStatusDetails)
| extend
DeviceName = tostring(DeviceDetailParsed.displayName),
Location = tostring(LocationDetailParsed.countryOrRegion),
SessionStatus = tostring(TokenProtection.signInSessionStatus)
| where SessionStatus == "unbound"
| project
NonInteractiveTime = TimeGenerated,
UserPrincipalName,
DeviceName,
AttackerIP = IPAddress,
AttackerLocation = Location,
AttackerASN = AutonomousSystemNumber,
ResourceDisplayName
) on UserPrincipalName, DeviceName
| where NonInteractiveTime between (InteractiveTime .. (InteractiveTime + TimeWindow))
| where VictimIP != AttackerIP
| project
InteractiveTime,
NonInteractiveTime,
TimeDiff = NonInteractiveTime - InteractiveTime,
UserPrincipalName,
DeviceName,
VictimIP,
AttackerIP,
VictimLocation,
AttackerLocation,
VictimASN,
AttackerASN,
ResourceDisplayName
Triage steps
- 1 Review the time difference between the bound and unbound authentications - very short intervals (under 5 minutes) are highly suspicious
- 2 Verify if the user was legitimately travelling or using a VPN between the two authentications
- 3 Check the IP reputation and ASN details for the attacker IP - look for VPS providers, proxies, or known malicious infrastructure
- 4 Compare the victim and attacker geolocations - impossible travel scenarios are strong indicators of compromise
- 5 Review the user's recent sign-in history for other suspicious activity or additional context switches
- 6 Verify if the user performed any copy/paste actions or visited suspicious URLs prior to the token protection switch
- 7 Check if multiple users show the same pattern, which could indicate a broader phishing campaign
- 8 If confirmed compromise, immediately revoke all active sessions and tokens for the affected user
- 9 Reset user credentials and review what resources were accessed using the unbound token
True positive example
- InteractiveTime
- 17/12/2025, 15:58:06.000
- NonInteractiveTime
- 17/12/2025, 15:58:56.000
- TimeDiff
- 00:00:50
- UserPrincipalName
- [email protected]
- DeviceName
- HUNTERWORK
- VictimIP
- 86.142.241.139
- AttackerIP
- 159.28.115.7
- VictimLocation
- GB
- AttackerLocation
- DK
- VictimASN
- 2856
- AttackerASN
- 208172
- ResourceDisplayName
- Azure Resource Manager
ConsentFix phishing attack detected - User authenticated from GB (86.142.241.139) with a bound PRT token, followed 50 seconds later by an unbound token authentication from DK (159.28.115.7). The rapid context switch from a compliant device to an unbound session, combined with the geographic discrepancy (GB to Denmark) and short time window, strongly indicates a stolen authorization code from a ConsentFix-style phishing attack. The attacker exchanged the stolen auth code for access tokens and immediately used them to access Azure Resource Manager.