Adversaries may be using Microsoft Entra ID apps with anomalous geolocation patterns to exfiltrate data or maintain persistent access. SOC teams should proactively hunt for this behavior in Azure Sentinel to identify potential credential compromise or lateral movement attempts.
KQL Query
let azureSignIns =
SigninLogs
| where SourceSystem == "Azure AD"
| where OperationName == "Sign-in activity"
| project TimeGenerated, OperationName, AppDisplayName , Identity, UserId, UserPrincipalName, Location, LocationDetails,
ClientAppUsed, DeviceDetail, ConditionalAccessPolicies;
azureSignIns
| extend locationString = strcat(tostring(LocationDetails["countryOrRegion"]), "/",
tostring(LocationDetails["state"]), "/", tostring(LocationDetails["city"]), ";" , tostring(LocationDetails["geoCoordinates"]))
| summarize rawSigninCount = count(), countByAccount = dcount(UserId), locationCount = dcount(locationString) by AppDisplayName
// tail - pick a threshold to rule out the very-high volume Azure AD apps
| where rawSigninCount < 1000
// more locations than accounts
| where locationCount>countByAccount
// almost as many / more locations than sign-ins!
| where 1.0*rawSigninCount / locationCount > 0.8
| order by rawSigninCount desc
| join kind = leftouter (
azureSignIns
) on AppDisplayName
| project AppDisplayName, TimeGenerated , Identity, rawSigninCount, countByAccount, locationCount,
locationString = strcat(tostring(LocationDetails["countryOrRegion"]), "/", tostring(LocationDetails["state"]), "/",
tostring(LocationDetails["city"]), ";" , tostring(LocationDetails["geoCoordinates"])), UserPrincipalName
| extend timestamp = TimeGenerated, AccountCustomEntity = UserPrincipalName
| order by AppDisplayName, TimeGenerated desc
id: 73ac88c0-f073-4b23-8ac4-9f40ea11308d
name: Anomalous Microsoft Entra ID apps based on authentication location
description: |
'This query over Microsoft Entra ID sign-in activity highlights Microsoft Entra ID apps with
an unusually high ratio of distinct geolocations versus total number of authentications'
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- SigninLogs
tactics:
- InitialAccess
relevantTechniques:
- T1078
query: |
let azureSignIns =
SigninLogs
| where SourceSystem == "Azure AD"
| where OperationName == "Sign-in activity"
| project TimeGenerated, OperationName, AppDisplayName , Identity, UserId, UserPrincipalName, Location, LocationDetails,
ClientAppUsed, DeviceDetail, ConditionalAccessPolicies;
azureSignIns
| extend locationString = strcat(tostring(LocationDetails["countryOrRegion"]), "/",
tostring(LocationDetails["state"]), "/", tostring(LocationDetails["city"]), ";" , tostring(LocationDetails["geoCoordinates"]))
| summarize rawSigninCount = count(), countByAccount = dcount(UserId), locationCount = dcount(locationString) by AppDisplayName
// tail - pick a threshold to rule out the very-high volume Azure AD apps
| where rawSigninCount < 1000
// more locations than accounts
| where locationCount>countByAccount
// almost as many / more locations than sign-ins!
| where 1.0*rawSigninCount / locationCount > 0.8
| order by rawSigninCount desc
| join kind = leftouter (
azureSignIns
) on AppDisplayName
| project AppDisplayName, TimeGenerated , Identity, rawSigninCount, countByAccount, locationCount,
locationString = strcat(tostring(LocationDetails["countryOrRegion"]), "/", tostring(LocationDetails["state"]), "/",
tostring(LocationDetails["city"]), ";" , tostring(LocationDetails["geoCoordinates"])), UserPrincipalName
| extend timestamp = TimeGenerated, AccountCustomEntity = UserPrincipalName
| order by AppDisplayName, TimeGenerated desc
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" ]
| Sentinel Table | Notes |
|---|---|
SigninLogs | Ensure this data connector is enabled |
Scenario: Scheduled backups using Azure Backup Service
Description: A legitimate backup job runs nightly and authenticates from a single geolocation, but the rule flags it due to the high ratio of distinct geolocations (e.g., if the backup tool authenticates from multiple regions during a multi-region backup process).
Filter/Exclusion: Exclude sign-ins from the Azure Backup Service (appDisplayName = "Azure Backup") or filter by clientAppUsed = "AzureBackup".
Scenario: System administrators using Microsoft Endpoint Manager (MEM) to deploy policies
Description: Admins use MEM to push policies to devices, which may involve authenticating from different geolocations depending on the device location or regional deployment.
Filter/Exclusion: Exclude sign-ins related to Microsoft Endpoint Manager (appDisplayName = "Microsoft Intune") or filter by userPrincipalName of known admin accounts.
Scenario: Automated CI/CD pipeline authenticating via Azure DevOps
Description: A CI/CD pipeline authenticates to Azure DevOps from multiple geolocations as it deploys to different regions or environments.
Filter/Exclusion: Exclude sign-ins from Azure DevOps services (appDisplayName = "Azure DevOps") or filter by clientAppUsed = "AzureDevOps".
Scenario: Remote access via Azure Bastion for secure RDP access
Description: Users connect to virtual machines via Azure Bastion, which may route traffic through different geolocations based on the user’s location or network configuration.
Filter/Exclusion: Exclude sign-ins related to Azure Bastion (appDisplayName = "Azure Bastion") or filter by clientAppUsed = "AzureBastion".
Scenario: Geolocation-based load balancing for global applications
Description: An application uses ge