Anomalous non-interactive token issuance after interactive sign-in (AiTM pattern)
Description
Identifies non-interactive sign-ins from a different IP and autonomous system than the preceding interactive sign-in for the same user within a 10-minute window, consistent with AiTM phishing token replay.
Query · kql
let timeframe = 1d;
let correlationWindow = 10m;
let InteractiveSessions =
SigninLogs
| where TimeGenerated >= ago(timeframe)
| where ResultType == 0
| where isnotempty(UserPrincipalName)
| summarize arg_max(TimeGenerated, IPAddress, AutonomousSystemNumber, CorrelationId)
by UserUpn = tolower(UserPrincipalName)
| project
UserUpn,
InteractiveTime = TimeGenerated,
InteractiveIp = IPAddress,
InteractiveAsn = AutonomousSystemNumber,
SessionId = CorrelationId;
AADNonInteractiveUserSignInLogs
| where TimeGenerated >= ago(timeframe)
| where ResultType == 0
| where isnotempty(UserPrincipalName)
| extend UserUpn = tolower(UserPrincipalName)
| join kind=inner InteractiveSessions on UserUpn
| where TimeGenerated between (InteractiveTime .. (InteractiveTime + correlationWindow))
| where IPAddress != InteractiveIp
| where AutonomousSystemNumber != InteractiveAsn
| extend AccountName = tostring(split(UserUpn, "@")[0])
| extend AccountUPNSuffix = tostring(split(UserUpn, "@")[1])
| project
TimeGenerated,
UserUpn,
AccountName,
AccountUPNSuffix,
AppDisplayName,
NonInteractiveIp = IPAddress,
NonInteractiveAsn = AutonomousSystemNumber,
InteractiveIp,
InteractiveAsn,
InteractiveTime,
SessionId,
CorrelationId
| sort by TimeGenerated desc