Adversaries may use sign-ins from new countries to establish initial access before performing sensitive operations to escalate privileges or modify security configurations. SOC teams should proactively hunt for this behavior in Azure Sentinel to detect potential credential compromise or unauthorized access attempts that could lead to deeper network infiltration.
KQL Query
let timeframe = 14d;
let lookback = 30d;
let correlationWindow = 1h;
let sensitiveOps = dynamic([
"Add member to role.",
"Consent to application",
"Add service principal credentials",
"Update application - Certificates and secrets management",
"Add application",
"Delete conditional access policy",
"Update conditional access policy",
"Set domain authentication",
"Add member to role (PIM activation)"
]);
// Build per-user baseline of countries from successful sign-ins over 30 days
let BaselineCountries =
SigninLogs
| where TimeGenerated >= ago(timeframe + lookback) and TimeGenerated < ago(timeframe)
| where ResultType == 0
| where isnotempty(Location)
| extend UserUpn = tolower(UserPrincipalName)
| summarize KnownCountries = make_set(Location) by UserUpn;
// Sign-ins from countries not in the user baseline
let NewCountrySignIns =
SigninLogs
| where TimeGenerated >= ago(timeframe)
| where ResultType == 0
| where isnotempty(Location)
| extend UserUpn = tolower(UserPrincipalName)
| join kind=leftouter BaselineCountries on $left.UserUpn == $right.UserUpn
| where isnull(KnownCountries) or not(set_has_element(KnownCountries, Location))
| project
SignInTime = TimeGenerated,
UserUpn,
SignInIP = IPAddress,
NewCountry = Location,
AppDisplayName;
// Sensitive AuditLogs operations correlated within the window
AuditLogs
| where TimeGenerated >= ago(timeframe)
| where OperationName in~ (sensitiveOps)
| where Result =~ "success"
| extend UserUpn = tolower(tostring(InitiatedBy.user.userPrincipalName))
| extend AuditIp = tostring(InitiatedBy.user.ipAddress)
| extend TargetName = tostring(TargetResources[0].displayName)
| where isnotempty(UserUpn)
| join kind=inner NewCountrySignIns on UserUpn
| where TimeGenerated between (SignInTime .. (SignInTime + correlationWindow))
| extend TimeDeltaMinutes = datetime_diff('minute', TimeGenerated, SignInTime)
| extend AccountName = tostring(split(UserUpn, "@")[0])
| extend AccountUPNSuffix = tostring(split(UserUpn, "@")[1])
| project
SignInTime,
OperationTime = TimeGenerated,
TimeDeltaMinutes,
UserUpn,
AccountName,
AccountUPNSuffix,
NewCountry,
SignInIP,
AuditIp,
OperationName,
TargetName,
AppDisplayName
| sort by SignInTime desc
id: 271f4bf9-e387-48ef-a537-654bd53ca8e8
name: Sign-in from new country followed by sensitive operation within one hour
description: |
Identifies successful sign-ins from a country absent in the user's 30-day history,
followed within one hour by a sensitive AuditLogs operation (role assignment, consent
grant, credential addition, CA policy change) by the same user. Correlates geographic
novelty with immediate high-value administrative action as a post-compromise signal.
References:
- https://learn.microsoft.com/azure/active-directory/reports-monitoring/reference-audit-activities
- https://learn.microsoft.com/azure/active-directory/reports-monitoring/concept-sign-ins
- https://attack.mitre.org/techniques/T1078/004/
- https://attack.mitre.org/techniques/T1098/
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- SigninLogs
- AuditLogs
tactics:
- InitialAccess
- Persistence
- PrivilegeEscalation
relevantTechniques:
- T1078.004
- T1098
query: |
let timeframe = 14d;
let lookback = 30d;
let correlationWindow = 1h;
let sensitiveOps = dynamic([
"Add member to role.",
"Consent to application",
"Add service principal credentials",
"Update application - Certificates and secrets management",
"Add application",
"Delete conditional access policy",
"Update conditional access policy",
"Set domain authentication",
"Add member to role (PIM activation)"
]);
// Build per-user baseline of countries from successful sign-ins over 30 days
let BaselineCountries =
SigninLogs
| where TimeGenerated >= ago(timeframe + lookback) and TimeGenerated < ago(timeframe)
| where ResultType == 0
| where isnotempty(Location)
| extend UserUpn = tolower(UserPrincipalName)
| summarize KnownCountries = make_set(Location) by UserUpn;
// Sign-ins from countries not in the user baseline
let NewCountrySignIns =
SigninLogs
| where TimeGenerated >= ago(timeframe)
| where ResultType == 0
| where isnotempty(Location)
| extend UserUpn = tolower(UserPrincipalName)
| join kind=leftouter BaselineCountries on $left.UserUpn == $right.UserUpn
| where isnull(KnownCountries) or not(set_has_element(KnownCountries, Location))
| project
SignInTime = TimeGenerated,
UserUpn,
SignInIP = IPAddress,
NewCountry = Location,
AppDisplayName;
// Sensitive AuditLogs operations correlated within the window
AuditLogs
| where TimeGenerated >= ago(timeframe)
| where OperationName in~ (sensitiveOps)
| where Result =~ "success"
| extend UserUpn = tolower(tostring(InitiatedBy.user.userPrincipalName))
| extend AuditIp = tostring(InitiatedBy.user.ipAddress)
| extend TargetName = tostring(TargetResources[0].displayName)
| where isnotempty(UserUpn)
| join kind=inner NewCountrySignIns on UserUpn
| where TimeGenerated between (Sig
| Sentinel Table | Notes |
|---|---|
AuditLogs | Ensure this data connector is enabled |
SigninLogs | Ensure this data connector is enabled |
Scenario: A user signs in from a new country due to a business trip and then performs a role assignment in Azure Active Directory (AAD) to grant access to a new team member.
Filter/Exclusion: Exclude sign-ins that occur within a known travel period or include a userTraveling flag in the user’s profile.
Scenario: A scheduled job in Azure Automation runs from a new country due to a regional deployment, and the job includes a credential addition to a new virtual machine.
Filter/Exclusion: Exclude sign-ins associated with automated processes or jobs that are flagged as system or scheduled in the sign-in logs.
Scenario: An admin user signs in from a new country to perform a consent grant for a new SaaS application, which is part of a routine onboarding process.
Filter/Exclusion: Exclude sign-ins where the user is marked as an admin and the operation is related to a known application onboarding process.
Scenario: A third-party service provider signs in from a new country to perform a credential addition for a customer’s account, as part of a support or maintenance task.
Filter/Exclusion: Exclude sign-ins from known service provider accounts or IP ranges associated with third-party support teams.
Scenario: A remote office employee signs in from a new country due to a temporary remote setup and then performs a CA (Cloud App) access assignment to a shared resource.
Filter/Exclusion: Exclude sign-ins that match the user’s recent location history or are associated with a temporary remote access policy.