An adversary may be attempting to compromise an account by initiating a sign-in from a new country with a correct password to evade detection. SOC teams should proactively hunt for this behavior in Azure Sentinel to identify potential account takeover attempts that bypass standard authentication controls.
KQL Query
// Creating a list of successful sign-in by users in the last 7 days.
let KnownUserCountry = (
SigninLogs
| where TimeGenerated between (ago(7d) .. ago(1d) )
| where ResultType == 0
| summarize KnownCountry = make_set(Location,1048576) by UserPrincipalName
);
// Identify sign-ins that are no successful but have the auth details indicating a correct password.
SigninLogs
| where TimeGenerated >= ago(1d)
| where ResultType != 0
| extend ParseAuth = parse_json(AuthenticationDetails)
| extend AuthMethod = tostring(ParseAuth.[0].authenticationMethod),
PasswordResult = tostring(ParseAuth.[0].authenticationStepResultDetail),
AuthSucceeded = tostring(ParseAuth.[0].succeeded)
| where PasswordResult == "Correct Password" or AuthSucceeded == "true"
| where AuthMethod == "Password"
| extend failureReason = tostring(Status.failureReason)
| summarize NewCountry = make_set(Location,1048576), LastObservedTime = max(TimeGenerated), AppName = make_set(AppDisplayName,1048576) by UserPrincipalName, PasswordResult, AuthSucceeded, failureReason
// Combining both tables by user
| join kind=inner KnownUserCountry on UserPrincipalName
// Compare both arrays and identify if the country has been observed in the past.
| extend CountryDiff = set_difference(NewCountry,KnownCountry)
| extend CountryDiffCount = array_length(CountryDiff)
// Count the new column to only alert if there is a difference between both arrays
| where CountryDiffCount != 0
| extend NewCountryEvent = CountryDiff
// Getting UserName and Domain
| extend Name = split(UserPrincipalName,"@",0),
Domain = split(UserPrincipalName,"@",1)
| mv-expand Name,Domain
id: 7808c05a-3afd-4d13-998a-a59e2297693f
name: New country signIn with correct password
description: |
'Identifies an interrupted sign-in session from a country the user has not sign-in before in the last 7 days, where the password was correct. Although the session is interrupted by other controls such as multi factor authentication or conditional access policies, the user credentials should be reset due to logs indicating a correct password was observed during sign-in.'
severity: Medium
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- SigninLogs
queryFrequency: 1d
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- CredentialAccess
relevantTechniques:
- T1078
- T1110
query: |
// Creating a list of successful sign-in by users in the last 7 days.
let KnownUserCountry = (
SigninLogs
| where TimeGenerated between (ago(7d) .. ago(1d) )
| where ResultType == 0
| summarize KnownCountry = make_set(Location,1048576) by UserPrincipalName
);
// Identify sign-ins that are no successful but have the auth details indicating a correct password.
SigninLogs
| where TimeGenerated >= ago(1d)
| where ResultType != 0
| extend ParseAuth = parse_json(AuthenticationDetails)
| extend AuthMethod = tostring(ParseAuth.[0].authenticationMethod),
PasswordResult = tostring(ParseAuth.[0].authenticationStepResultDetail),
AuthSucceeded = tostring(ParseAuth.[0].succeeded)
| where PasswordResult == "Correct Password" or AuthSucceeded == "true"
| where AuthMethod == "Password"
| extend failureReason = tostring(Status.failureReason)
| summarize NewCountry = make_set(Location,1048576), LastObservedTime = max(TimeGenerated), AppName = make_set(AppDisplayName,1048576) by UserPrincipalName, PasswordResult, AuthSucceeded, failureReason
// Combining both tables by user
| join kind=inner KnownUserCountry on UserPrincipalName
// Compare both arrays and identify if the country has been observed in the past.
| extend CountryDiff = set_difference(NewCountry,KnownCountry)
| extend CountryDiffCount = array_length(CountryDiff)
// Count the new column to only alert if there is a difference between both arrays
| where CountryDiffCount != 0
| extend NewCountryEvent = CountryDiff
// Getting UserName and Domain
| extend Name = split(UserPrincipalName,"@",0),
Domain = split(UserPrincipalName,"@",1)
| mv-expand Name,Domain
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: UserPrincipalName
- identifier: Name
columnName: Name
- identifier: NTDomain
columnName: Domain
customDetails:
LastObservedTime: LastObservedTime
AppName: AppName
NewCountryEvent: NewCountryEvent
PasswordResult: PasswordResult
AuthSucceeded: AuthSucceeded
failureReason: failureReason
eventGroupingSettings:
aggregationKind: SingleAlert
version: 1.0.2
kind: Scheduled
metadata:
sou
| Sentinel Table | Notes |
|---|---|
SigninLogs | Ensure this data connector is enabled |
Scenario: Scheduled Job from a New Country
Description: A legitimate scheduled job (e.g., AWS EC2 instance maintenance, Azure VM scale set update) runs from a new country due to geographic load balancing or failover.
Filter/Exclusion: Exclude IP addresses associated with cloud provider infrastructure (e.g., aws.amazon.com, azure.com) or use a filter like src_ip in (list of known cloud provider IPs).
Scenario: Remote Administration Tool (RAT) from a New Country
Description: A system administrator uses a remote administration tool (e.g., PsExec, SSH, TeamViewer) from a new country to troubleshoot a remote server.
Filter/Exclusion: Exclude IP addresses associated with known remote administration tools or use a filter like user_agent contains "TeamViewer" or process_name contains "PsExec".
Scenario: User Traveling for Business
Description: A user signs in from a new country due to business travel, but the sign-in is interrupted by multi-factor authentication (MFA) or a security policy check.
Filter/Exclusion: Exclude sign-ins where the user has a travel history or use a filter like user_has_travel_history = true or user_location_known = true.
Scenario: Admin Task from a New Country
Description: A system administrator performs an admin task (e.g., using PowerShell, Ansible, or Chef) from a new country to manage a distributed network.
Filter/Exclusion: Exclude IP addresses associated with admin tools or use a filter like process_name contains "PowerShell" or user_has_admin_role = true.
Scenario: False Positive from a Security Tool Sign-In
Description: A security tool (e.g., CrowdStrike, Palo Alto Networks, or Microsoft