← Back to SOC feed Coverage →

Microsoft Entra ID sign-in burst from multiple locations

kql MEDIUM Azure-Sentinel
T1110
SigninLogs
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-04T23:00:00Z · Confidence: medium

Hunt Hypothesis

Adversaries may be using compromised accounts to perform rapid, coordinated sign-ins from multiple geographic locations to evade detection. SOC teams should proactively hunt for this behavior in Azure Sentinel to identify potential credential compromise or lateral movement attempts.

KQL Query


let starttime = todatetime('{{StartTimeISO}}');
let endtime = todatetime('{{EndTimeISO}}');
let common_locations = (SigninLogs
  | where TimeGenerated between(starttime..endtime)
  | extend locationString= strcat(tostring(LocationDetails["countryOrRegion"]), "/",
  tostring(LocationDetails["state"]))
  | where locationString != "//"
  | summarize count() by locationString
  | take 100
  | project locationString);
let signIns = (SigninLogs
  | where TimeGenerated between(starttime..endtime)
  | extend locationString= strcat(tostring(LocationDetails["countryOrRegion"]), "/",
  tostring(LocationDetails["state"]))
  | where locationString != "//" and locationString !endswith "/"
  | where locationString !in (common_locations));
// Adjust these to tune query
let lookupWindow = 10m;
let lookupBin = lookupWindow / 2.0; // lookup bin = equal to 1/2 of the lookup window
let threshold = 2;
let users = (signIns
| summarize dcount(locationString) by Identity
| where dcount_locationString > threshold
| project Identity);
  signIns
  | where Identity in (users)
  | project-rename Start=TimeGenerated
  | extend TimeKey = bin(Start, lookupBin)
  | join kind = inner (
  signIns
  | project-rename End=TimeGenerated, EndLocationString=locationString
    // TimeKey on the right side of the join - emulates this authentication appearing several times
    | extend TimeKey = range(bin(End - lookupWindow, lookupBin),
    bin(End, lookupBin), lookupBin)
    | mvexpand TimeKey to typeof(datetime) // translate TimeKey arrange range to a column
  ) on Identity, TimeKey
  | where End > Start
  | project tostring(Start), tostring(End), locationString, EndLocationString, UserPrincipalName, timeSpan = End - Start, Identity, IPAddress, UserAgent
  | where locationString != EndLocationString
  | summarize ips=makeset(IPAddress), UAs=makeset(UserAgent) by timeSpan, Identity, locationString, EndLocationString, Start, End, UserPrincipalName
  | extend timestamp = Start, AccountCustomEntity = UserPrincipalName
  | order by Identity

Analytic Rule Definition

id: 745a22ec-fed8-49b9-9f62-4570b7709da4
name: Microsoft Entra ID sign-in burst from multiple locations
description: |
  'Highlights accounts associated with multiple authentications from different geographical locations in a short period of time.'
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - SigninLogs
tactics:
  - CredentialAccess
relevantTechniques:
  - T1110
query: |

  let starttime = todatetime('{{StartTimeISO}}');
  let endtime = todatetime('{{EndTimeISO}}');
  let common_locations = (SigninLogs
    | where TimeGenerated between(starttime..endtime)
    | extend locationString= strcat(tostring(LocationDetails["countryOrRegion"]), "/",
    tostring(LocationDetails["state"]))
    | where locationString != "//"
    | summarize count() by locationString
    | take 100
    | project locationString);
  let signIns = (SigninLogs
    | where TimeGenerated between(starttime..endtime)
    | extend locationString= strcat(tostring(LocationDetails["countryOrRegion"]), "/",
    tostring(LocationDetails["state"]))
    | where locationString != "//" and locationString !endswith "/"
    | where locationString !in (common_locations));
  // Adjust these to tune query
  let lookupWindow = 10m;
  let lookupBin = lookupWindow / 2.0; // lookup bin = equal to 1/2 of the lookup window
  let threshold = 2;
  let users = (signIns
  | summarize dcount(locationString) by Identity
  | where dcount_locationString > threshold
  | project Identity);
    signIns
    | where Identity in (users)
    | project-rename Start=TimeGenerated
    | extend TimeKey = bin(Start, lookupBin)
    | join kind = inner (
    signIns
    | project-rename End=TimeGenerated, EndLocationString=locationString
      // TimeKey on the right side of the join - emulates this authentication appearing several times
      | extend TimeKey = range(bin(End - lookupWindow, lookupBin),
      bin(End, lookupBin), lookupBin)
      | mvexpand TimeKey to typeof(datetime) // translate TimeKey arrange range to a column
    ) on Identity, TimeKey
    | where End > Start
    | project tostring(Start), tostring(End), locationString, EndLocationString, UserPrincipalName, timeSpan = End - Start, Identity, IPAddress, UserAgent
    | where locationString != EndLocationString
    | summarize ips=makeset(IPAddress), UAs=makeset(UserAgent) by timeSpan, Identity, locationString, EndLocationString, Start, End, UserPrincipalName
    | extend timestamp = Start, AccountCustomEntity = UserPrincipalName
    | order by Identity

entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: AccountCustomEntity
version: 1.0.0
metadata:
    source:
        kind: Community
    author:
        name: Shain
    support:
        tier: Community
    categories:
        domains: [ "Security - Other", "Identity" ]

Required Data Sources

Sentinel TableNotes
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/SigninLogs/signinBurstFromMultipleLocations.yaml