Token Protection Switch with Different IP
Platform
Data Sources
Related Blog Post
Read the full blog post about this hunt →MITRE ATT&CK
Tactics
Techniques
Threat Actors
Tags
Hunt 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
Analytic #1This 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.
Detection Queries
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
- Review the time difference between the bound and unbound authentications - very short intervals (under 5 minutes) are highly suspicious
- Verify if the user was legitimately travelling or using a VPN between the two authentications
- Check the IP reputation and ASN details for the attacker IP - look for VPS providers, proxies, or known malicious infrastructure
- Compare the victim and attacker geolocations - impossible travel scenarios are strong indicators of compromise
- Review the user's recent sign-in history for other suspicious activity or additional context switches
- Verify if the user performed any copy/paste actions or visited suspicious URLs prior to the token protection switch
- Check if multiple users show the same pattern, which could indicate a broader phishing campaign
- If confirmed compromise, immediately revoke all active sessions and tokens for the affected user
- 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": "hunter1@redacted.onmicrosoft.com",
"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.