Query Details

16 SIGNIN Concurrent Multi Country Sessions

Query

id: f6a7b8c9-d0e1-4f2a-3b4c-5d6e7f8a9b0c
name: "SigninLogs — Concurrent Sessions from Multiple Countries (Same User)"
version: 1.0.0
kind: Scheduled
description: |
  Detects a single user successfully signing in from 2 or more distinct countries within a 30-minute window. Unlike ML-based impossible travel this is deterministic, requires no Entra ID P2, and catches concurrent attacker + victim sessions running in parallel from different geos. MITRE ATT&CK: T1078 (Valid Accounts).
severity: High
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - SigninLogs
queryFrequency: PT1H
queryPeriod: PT1H
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
  - CredentialAccess
relevantTechniques:
  - T1078
query: |

    // ---- Network Allowlist (exclude trusted IPs / CIDR / ranges) --------------
    let _allow = materialize(union isfuzzy=true (print R="" | take 0), (_GetWatchlist('NetworkAllowlist') | project R = tostring(IPOrRange)) | where isnotempty(R));
    let _allowCIDR  = toscalar(_allow | where not(R matches regex @'^\d+\.\d+\.\d+\.\d+-\d+\.\d+\.\d+\.\d+$') | extend R = iff(R has '/', R, strcat(R, '/32')) | summarize make_list(R));
    let _allowRange = toscalar(_allow | where R matches regex @'^\d+\.\d+\.\d+\.\d+-\d+\.\d+\.\d+\.\d+$' | summarize make_list(R));
    let _ExcludeAllowlistedIPs = (T:(IPAddress:string)) {
        T
        | extend IPAddress = tostring(IPAddress)
        | where array_length(_allowCIDR) == 0 or isnull(ipv4_is_in_any_range(IPAddress, _allowCIDR)) or not(ipv4_is_in_any_range(IPAddress, _allowCIDR))
        | mv-apply _r = _allowRange to typeof(string) on (
            extend _lo = tostring(split(_r,'-')[0]), _hi = tostring(split(_r,'-')[1])
            | extend _inRange = ipv4_compare(IPAddress, _lo) >= 0 and ipv4_compare(IPAddress, _hi) <= 0
            | summarize _anyInRange = max(toint(_inRange)))
        | where isnull(_anyInRange) or _anyInRange == 0
        | project-away _anyInRange
    };
    // ---------------------------------------------------------------------------
    let lookback   = 1h;
    let maxWindowM = 30;
    SigninLogs
    | invoke _ExcludeAllowlistedIPs()
    | where TimeGenerated > ago(lookback)
    | where ResultType == "0"
    | where isnotempty(Location) and Location != "Unknown"
    | summarize
        UniqueCountries = dcount(Location),
        Countries       = make_set(Location, 10),
        SourceIPs       = make_set(IPAddress, 10),
        Apps            = make_set(AppDisplayName, 5),
        FirstSignIn     = min(TimeGenerated),
        LastSignIn      = max(TimeGenerated)
      by UserPrincipalName
    | where UniqueCountries >= 2
    | extend WindowMinutes = datetime_diff("minute", LastSignIn, FirstSignIn)
    | where WindowMinutes <= maxWindowM
    | project
        TimeGenerated = FirstSignIn,
        UserPrincipalName, UniqueCountries,
        Countries, SourceIPs, WindowMinutes,
        Apps, LastSignIn
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: UserPrincipalName
customDetails:
  UniqueCountries: UniqueCountries
  Countries: Countries
  WindowMinutes: WindowMinutes
alertDetailsOverride:
  alertDisplayNameFormat: "Concurrent Multi-Country Sessions — {{UserPrincipalName}} in {{UniqueCountries}} countries ({{WindowMinutes}}min)"
  alertDescriptionFormat: "User {{UserPrincipalName}} authenticated from {{UniqueCountries}} different countries within {{WindowMinutes}} minutes — possible concurrent attacker session."
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: PT1H
    matchingMethod: AllEntities
    groupByEntities:
  - Account



Explanation

This query is designed to detect suspicious login activity by identifying instances where a single user successfully signs in from two or more different countries within a 30-minute window. This could indicate potential unauthorized access, such as an attacker using the same credentials as the legitimate user but from a different location.

Here's a simple breakdown of the query's components:

  1. Network Allowlist: The query first defines a list of trusted IP addresses or ranges (allowlist) to exclude them from the analysis. This helps in focusing only on potentially suspicious activities.

  2. Data Source: It uses data from Azure Active Directory's SigninLogs to analyze user sign-ins.

  3. Time Frame: The query looks at sign-in activities within the last hour (lookback) and checks if multiple sign-ins occurred within a 30-minute window (maxWindowM).

  4. Filter Criteria:

    • Excludes sign-ins from allowlisted IPs.
    • Considers only successful sign-ins (ResultType == "0").
    • Ignores sign-ins from unknown locations.
  5. Analysis:

    • Groups sign-ins by user and counts the number of distinct countries from which the user signed in.
    • Checks if the user signed in from at least two different countries within the specified time window.
  6. Output: If such activity is detected, it generates an alert with details like the user's name, the number of countries, the IP addresses used, the applications accessed, and the time of the first and last sign-in.

  7. Alert Configuration: The alert is configured to create an incident if such activity is detected, with details formatted to highlight the user's name, the number of countries involved, and the time window of the activity.

Overall, this query helps in identifying potential security threats by flagging unusual sign-in patterns that could indicate concurrent sessions from different geographical locations, possibly involving an attacker.

Details

David Alonso profile picture

David Alonso

Released: April 20, 2026

Tables

SigninLogs

Keywords

SigninLogsAzureActiveDirectoryUserAccountLocationIPAddressAppDisplayNameTimeGeneratedUserPrincipalNameCountriesSourceIPsApps

Operators

letmaterializeunionisfuzzyprinttakeprojectwhereisnotemptytoscalarextendiffstrcatsummarizemake_listmatchesregexnotarray_lengthisnullipv4_is_in_any_rangemv-applytotypeofonsplitipv4_comparemaxtointproject-awayagoinvokedcountmake_setminmaxbydatetime_diffproject

Actions