hunt-2025-038 v1.0 by Paul Newton

Token Protection Switch with Different IP

Platform

Entra

Data Sources

AADNonInteractiveUserSignInLogs SigninLogs

MITRE ATT&CK

Tactics

Initial Access

Techniques

Threat Actors

unattributed

Tags

#entra #cloud #identity #oauth #phishing

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

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.

Detection Queries

KQL
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

Log Entry:
{
  "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"
}
Analysis:

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.