Adversaries may use anomalous sign-in locations to mask their activity and evade detection by mimicking legitimate user behavior. SOC teams should proactively hunt for this behavior in Azure Sentinel to identify potential account compromise or lateral movement attempts.
KQL Query
SigninLogs
// Forces Log Analytics to recognize that the query should be run over full time range
| extend locationString= strcat(tostring(LocationDetails["countryOrRegion"]), "/",
tostring(LocationDetails["state"]), "/", tostring(LocationDetails["city"]), ";")
| project TimeGenerated, AppDisplayName , UserPrincipalName, locationString
// Create time series
| make-series dLocationCount = dcount(locationString) on TimeGenerated step 1d
by UserPrincipalName, AppDisplayName
// Compute best fit line for each entry
| extend (RSquare,Slope,Variance,RVariance,Interception,LineFit)=series_fit_line(dLocationCount)
// Chart the 3 most interesting lines
// A 0-value slope corresponds to an account being completely stable over time for a given Azure Active Directory application
| top 3 by Slope desc
// Extract the set of locations for each top user:
| join kind=inner (SigninLogs
| extend locationString= strcat(tostring(LocationDetails["countryOrRegion"]), "/",
tostring(LocationDetails["state"]), "/", tostring(LocationDetails["city"]), ";")
| summarize locationList = makeset(locationString), threeDayWindowLocationCount=dcount(locationString) by AppDisplayName, UserPrincipalName,
timerange=bin(TimeGenerated, 3d)) on AppDisplayName, UserPrincipalName
| order by UserPrincipalName, timerange asc
| project timerange, AppDisplayName , UserPrincipalName, threeDayWindowLocationCount, locationList
| order by AppDisplayName, UserPrincipalName, timerange asc
| extend timestamp = timerange, AccountCustomEntity = UserPrincipalName
id: 7f6e8f14-62fa-4ce6-a490-c07f1d9888ba
name: Anomalous sign-in location by user account and authenticating application - with sign-in details
description: |
'This query examines Microsoft Entra ID sign-ins and identifies anomalous changes in a user's location profile. A variation joins results back onto the original sign-in data to review the location set with each identified user in tabular form.'
description_detailed: |
'This query over Microsoft Entra ID sign-in considers all user sign-ins for each Azure Active
Directory application and picks out the most anomalous change in location profile for a user within an
individual application. The intent is to hunt for user account compromise, possibly via a specific application
vector.
This variation of the query joins the results back onto the original sign-in data to allow review of the
location set with each identified user in tabular form.'
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- SigninLogs
tactics:
- InitialAccess
relevantTechniques:
- T1078
query: |
SigninLogs
// Forces Log Analytics to recognize that the query should be run over full time range
| extend locationString= strcat(tostring(LocationDetails["countryOrRegion"]), "/",
tostring(LocationDetails["state"]), "/", tostring(LocationDetails["city"]), ";")
| project TimeGenerated, AppDisplayName , UserPrincipalName, locationString
// Create time series
| make-series dLocationCount = dcount(locationString) on TimeGenerated step 1d
by UserPrincipalName, AppDisplayName
// Compute best fit line for each entry
| extend (RSquare,Slope,Variance,RVariance,Interception,LineFit)=series_fit_line(dLocationCount)
// Chart the 3 most interesting lines
// A 0-value slope corresponds to an account being completely stable over time for a given Azure Active Directory application
| top 3 by Slope desc
// Extract the set of locations for each top user:
| join kind=inner (SigninLogs
| extend locationString= strcat(tostring(LocationDetails["countryOrRegion"]), "/",
tostring(LocationDetails["state"]), "/", tostring(LocationDetails["city"]), ";")
| summarize locationList = makeset(locationString), threeDayWindowLocationCount=dcount(locationString) by AppDisplayName, UserPrincipalName,
timerange=bin(TimeGenerated, 3d)) on AppDisplayName, UserPrincipalName
| order by UserPrincipalName, timerange asc
| project timerange, AppDisplayName , UserPrincipalName, threeDayWindowLocationCount, locationList
| order by AppDisplayName, UserPrincipalName, timerange asc
| extend timestamp = timerange, AccountCustomEntity = UserPrincipalName
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: AccountCustomEntity
version: 1.0.1
metadata:
source:
kind: Community
author:
name: Shain
support:
tier: Community
categories:
domains: [ "Security - Other", "Iden
| Sentinel Table | Notes |
|---|---|
SigninLogs | Ensure this data connector is enabled |
Scenario: Remote Desktop Services (RDS) Sign-In from a Known Location
Description: A user signs in via Remote Desktop Services (RDS) from a location that is part of their regular remote access setup.
Filter/Exclusion: where UserAgent contains "mstsc.exe" or where ClientAppId == "microsoft-remote-desktop"
Scenario: Scheduled Job Execution via PowerShell or Task Scheduler
Description: A system administrator runs a scheduled PowerShell script or Task Scheduler job that authenticates to Microsoft Entra ID for automation purposes.
Filter/Exclusion: where ClientAppId == "microsoft-powershell" or where ClientAppId == "microsoft-task-scheduler"
Scenario: Admin Task via Azure CLI or Azure PowerShell
Description: An admin uses Azure CLI or Azure PowerShell to perform administrative tasks that require sign-in to Microsoft Entra ID.
Filter/Exclusion: where ClientAppId == "azure-cli" or where ClientAppId == "azure-powershell"
Scenario: User Access from a Corporate Travel Location
Description: A user is traveling for business and signs in from a location that is not their usual location but is known to be a valid travel destination.
Filter/Exclusion: where UserLocation in ("Valid Travel Locations") or where UserLocation contains "Travel" or "Corporate Travel"
Scenario: Sign-In from a Multi-Factor Authentication (MFA) Verification Service
Description: A user signs in from an MFA verification service (e.g., Microsoft Authenticator, Google Authenticator) which may show a different IP location.
Filter/Exclusion: where UserAgent contains "Microsoft Authenticator" or where UserAgent contains "Google Authenticator"