← Back to SOC feed Coverage →

Short-window sign-in mismatch between interactive and non-interactive activity

kql MEDIUM Azure-Sentinel
T1550.001T1539
AADNonInteractiveUserSignInLogsSigninLogs
huntingmicrosoftofficial
This rule was pulled from an open-source repository and enriched with AI. Validate in a test environment before deploying to production.
View original rule at Azure-Sentinel →
Retrieved: 2026-06-03T11:00:00Z · Confidence: medium

Hunt Hypothesis

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

Analytic Rule Definition

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

Required Data Sources

Sentinel TableNotes
AADNonInteractiveUserSignInLogsEnsure this data connector is enabled
SigninLogsEnsure this data connector is enabled

MITRE ATT&CK Context

References

False Positive Guidance

Original source: https://github.com/Azure/Azure-Sentinel/blob/main/Hunting Queries/MultipleDataSources/SignInASNMismatchInteractiveVsNonInteractive.yaml