Adversaries may use a non-interactive sign-in from a different ASN and IP within 10 minutes of an interactive sign-in to pivot or maintain access. SOC teams should proactively hunt for this pattern to detect potential post-compromise activity and lateral movement in their Azure Sentinel environment.
KQL Query
let starttime = todatetime('{{StartTimeISO}}');
let endtime = todatetime('{{EndTimeISO}}');
let window = 10m;
let interactive =
SigninLogs
| where TimeGenerated between (starttime .. endtime)
| where ResultType == 0
| project
InteractiveTime = TimeGenerated,
UserPrincipalName = tolower(UserPrincipalName),
InteractiveIP = IPAddress,
InteractiveASN = AutonomousSystemNumber,
InteractiveAppDisplayName = AppDisplayName,
InteractiveCorrelationId = CorrelationId;
let nonInteractive =
AADNonInteractiveUserSignInLogs
| where TimeGenerated between (starttime .. endtime)
| where ResultType == 0
| project
NonInteractiveTime = TimeGenerated,
UserPrincipalName = tolower(UserPrincipalName),
NonInteractiveIP = IPAddress,
NonInteractiveASN = AutonomousSystemNumber,
NonInteractiveAppDisplayName = AppDisplayName,
NonInteractiveCorrelationId = CorrelationId;
interactive
| join kind=inner nonInteractive on UserPrincipalName
| where NonInteractiveTime between (InteractiveTime .. (InteractiveTime + window))
| where isnotempty(InteractiveASN) and isnotempty(NonInteractiveASN)
| where InteractiveASN != NonInteractiveASN
| where InteractiveIP != NonInteractiveIP
| extend TimeDeltaSeconds = datetime_diff('second', NonInteractiveTime, InteractiveTime)
| extend AccountName = tostring(split(UserPrincipalName, "@")[0]),
AccountUPNSuffix = tostring(split(UserPrincipalName, "@")[1])
| project
InteractiveTime,
NonInteractiveTime,
TimeDeltaSeconds,
UserPrincipalName,
AccountName,
AccountUPNSuffix,
InteractiveIP,
NonInteractiveIP,
InteractiveASN,
NonInteractiveASN,
InteractiveAppDisplayName,
NonInteractiveAppDisplayName,
InteractiveCorrelationId,
NonInteractiveCorrelationId
| sort by InteractiveTime desc
id: 868599d4-84f7-4c31-ba00-d2a2c87efaab
name: Short-window sign-in mismatch between interactive and non-interactive activity
description: |
Identify successful sign-ins where a non-interactive sign-in for the same user occurs
within 10 minutes from a different ASN and IP than the interactive sign-in. This pattern
can indicate post-compromise token misuse and requires analyst validation.
description-detailed: |
This hypothesis-driven Microsoft Sentinel hunting query surfaces short-window mismatches
between successful interactive sign-ins (SigninLogs) and successful non-interactive sign-ins
(AADNonInteractiveUserSignInLogs) for the same user, where the originating Autonomous System
Number (ASN) and IP address differ within a 10-minute window. This pattern can correspond to
a possible post-compromise session misuse scenario in which previously issued authentication
material is being used from a different network origin shortly after an interactive sign-in.
This query does not observe credential or session theft itself, and it does not claim
definitive adversary-in-the-middle behavior. Analysts must validate every result.
Expect benign matches in scenarios such as VPN connect/disconnect, user roaming between
networks, mobile application background token refresh, multi-device usage, and corporate
proxies or egress points that change the public ASN as traffic shifts between services.
References:
- https://learn.microsoft.com/azure/active-directory/reports-monitoring/concept-sign-ins
- https://learn.microsoft.com/azure/active-directory/reports-monitoring/concept-all-sign-ins
- https://attack.mitre.org/techniques/T1550/001/
- https://attack.mitre.org/techniques/T1539/
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- SigninLogs
- AADNonInteractiveUserSignInLogs
tactics:
- CredentialAccess
relevantTechniques:
- T1550.001
- T1539
query: |
let starttime = todatetime('{{StartTimeISO}}');
let endtime = todatetime('{{EndTimeISO}}');
let window = 10m;
let interactive =
SigninLogs
| where TimeGenerated between (starttime .. endtime)
| where ResultType == 0
| project
InteractiveTime = TimeGenerated,
UserPrincipalName = tolower(UserPrincipalName),
InteractiveIP = IPAddress,
InteractiveASN = AutonomousSystemNumber,
InteractiveAppDisplayName = AppDisplayName,
InteractiveCorrelationId = CorrelationId;
let nonInteractive =
AADNonInteractiveUserSignInLogs
| where TimeGenerated between (starttime .. endtime)
| where ResultType == 0
| project
NonInteractiveTime = TimeGenerated,
UserPrincipalName = tolower(UserPrincipalName),
NonInteractiveIP = IPAddress,
NonInteractiveASN = AutonomousSystemNumber,
NonInteractiveAppDisplayName = AppDisplayName,
NonInteractiveCorrelationId = CorrelationId;
interactive
| join k
| Sentinel Table | Notes |
|---|---|
AADNonInteractiveUserSignInLogs | Ensure this data connector is enabled |
SigninLogs | Ensure this data connector is enabled |
Scenario: A system administrator performs an interactive sign-in from their office IP, then shortly after runs a scheduled backup job that authenticates non-interactively using a service account from a different ASN (e.g., a cloud provider’s IP range).
Filter/Exclusion: Exclude sign-ins associated with known service accounts or scheduled tasks (e.g., @ScheduledTask or @BackupJob in Azure AD logs).
Scenario: A developer uses a local interactive sign-in to access a development environment, then triggers a CI/CD pipeline (e.g., Jenkins) that authenticates non-interactively from a different IP (e.g., a CI/CD server in a different region).
Filter/Exclusion: Exclude sign-ins from CI/CD tools (e.g., Jenkins, GitHub Actions, or Azure DevOps) or use a custom field like @ci_cd_pipeline.
Scenario: A user logs in interactively to access a remote desktop session (e.g., using Microsoft Remote Desktop), and then a non-interactive sign-in occurs from a different IP due to a background sync job (e.g., Microsoft Intune or Azure AD Connect).
Filter/Exclusion: Exclude sign-ins related to device synchronization or management tools (e.g., Intune, Azure AD Connect, or Microsoft Endpoint Manager).
Scenario: A user signs in interactively to a company laptop, and shortly after, a non-interactive sign-in occurs from a different IP due to a system update or patching task (e.g., Windows Update or SCCM).
Filter/Exclusion: Exclude sign-ins associated with patching or update tools (e.g., Windows Update, SCCM, or Ansible).
Scenario: An admin uses an interactive sign-in to access a management console (e.g., AWS Console), and then a non-interactive sign-in