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.'

Query · kql

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
Raw source Microsoft Entra ID sign-in burst from multiple locations · KQL
Esc
Published by Azure/Azure-Sentinel ↗, licensed under MIT ↗. Reproduced here unmodified.
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" ]

Detection rules belong to the projects that publish them and remain under their own licenses. This site indexes and links to them; it claims no rights in them.