← Back to SOC feed Coverage →

Anomalous Microsoft Entra ID apps based on authentication location

kql MEDIUM Azure-Sentinel
T1078
SigninLogs
backdoorhuntingmicrosoftofficial
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 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

Analytic Rule Definition

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" ]

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/anomalous_app_azuread_signin.yaml