An adversary may be using an interactive sign-in to obtain credentials and then leveraging a non-interactive token issuance from a different IP and autonomous system to maintain access or exfiltrate data. SOC teams should proactively hunt for this pattern in Azure Sentinel to detect potential AiTM phishing attacks that bypass traditional authentication controls.
KQL Query
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
id: 2a2c676e-885f-43d9-90cb-2c035772e31c
name: 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.
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- SigninLogs
- AADNonInteractiveUserSignInLogs
tactics:
- InitialAccess
- CredentialAccess
relevantTechniques:
- T1539
- T1550
query: |
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
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: UserUpn
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: NonInteractiveIp
version: 1.0.0
metadata:
source:
kind: Community
author:
name: descambiado
support:
tier: Community
categories:
domains: [ "Security - Threat Protection", "Identity" ]
| Sentinel Table | Notes |
|---|---|
AADNonInteractiveUserSignInLogs | Ensure this data connector is enabled |
SigninLogs | Ensure this data connector is enabled |
Scenario: A system administrator uses interactive sign-in to access Azure AD, then shortly after runs a scheduled job that issues a non-interactive token to a service account from a different IP (e.g., due to a multi-factor authentication (MFA) prompt redirecting the session).
Filter/Exclusion: Exclude non-interactive tokens issued by known service accounts or scheduled tasks (e.g., runbook or Azure Automation jobs) using the userPrincipalName or clientApplicationId field.
Scenario: A developer uses interactive sign-in to access a development environment, then uses a local script (e.g., PowerShell or Python) to request a non-interactive token for a CI/CD pipeline tool like Azure DevOps or Jenkins from a different IP.
Filter/Exclusion: Exclude tokens issued by CI/CD tools or development environments by checking the clientApplicationId against known CI/CD service principals.
Scenario: A user performs an interactive sign-in, then uses a third-party tool like Azure CLI or Azure PowerShell to request a non-interactive token for a cloud resource from a different IP, such as a remote development machine.
Filter/Exclusion: Exclude tokens issued by known CLI tools or scripts by checking the clientApplicationId or userAgent field against known CLI or script-based tools.
Scenario: An admin performs an interactive sign-in, then uses a remote desktop session (e.g., RDP or SSH) to access a server and runs a script that requests a non-interactive token for a local service from a different IP.
Filter/Exclusion: Exclude tokens issued from remote sessions by checking the ipAddress against known admin workstations or using sessionID or deviceName fields.
Scenario: A user signs in interactively