Adversaries may be using an IP address to perform credential stuffing attacks by repeatedly attempting to sign in with valid credentials across multiple users. SOC teams should proactively hunt for this behavior in Azure Sentinel to identify potential account compromise and prevent lateral movement.
KQL Query
let LookBack = 1h;
let Data = (
SigninLogs
| where TimeGenerated >= ago(LookBack)
| where parse_json(NetworkLocationDetails)[0].networkType != "trustedNamedLocation" // Excludes known tagged networks
// Counts the number of sign in events in the last hour every 15 minutes by IP
| make-series EventCounts = count() on TimeGenerated from ago(LookBack) to now() step 15m by IPAddress
);
let AnomalyAlert = (
Data
| extend (Anomalies, Score, Baseline) = series_decompose_anomalies(EventCounts,1.5,-1,'linefit')
| mv-expand EventCounts,TimeGenerated,Anomalies to typeof(double),Baseline to typeof(long),Score to typeof(double)
| where Anomalies > 0
);
AnomalyAlert
| join kind = inner (SigninLogs
| where TimeGenerated between (ago(LookBack) .. now())
| where parse_json(NetworkLocationDetails)[0].networkType != "trustedNamedLocation"
| extend PasswordResult = tostring(parse_json(AuthenticationDetails).authenticationStepResultDetail)
| summarize UserCount = dcount(UserPrincipalName), UserList = make_set(UserPrincipalName), AppName = make_set(AppDisplayName), PasswordResult = make_list(PasswordResult) by IPAddress) on IPAddress
| where PasswordResult has "Correct Password"
| where UserCount > 1 // looks for events targeting more than one user.
id: 9c1e9381-79dd-4ddf-9570-b73a1dc59fe0
name: Anomaly Sign In Event from an IP
description: |
'Identifies sign-in anomalies from an IP in the last hour, targeting multiple users where the password is correct after multiple attempts'
severity: Medium
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- SigninLogs
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1078
query: |
let LookBack = 1h;
let Data = (
SigninLogs
| where TimeGenerated >= ago(LookBack)
| where parse_json(NetworkLocationDetails)[0].networkType != "trustedNamedLocation" // Excludes known tagged networks
// Counts the number of sign in events in the last hour every 15 minutes by IP
| make-series EventCounts = count() on TimeGenerated from ago(LookBack) to now() step 15m by IPAddress
);
let AnomalyAlert = (
Data
| extend (Anomalies, Score, Baseline) = series_decompose_anomalies(EventCounts,1.5,-1,'linefit')
| mv-expand EventCounts,TimeGenerated,Anomalies to typeof(double),Baseline to typeof(long),Score to typeof(double)
| where Anomalies > 0
);
AnomalyAlert
| join kind = inner (SigninLogs
| where TimeGenerated between (ago(LookBack) .. now())
| where parse_json(NetworkLocationDetails)[0].networkType != "trustedNamedLocation"
| extend PasswordResult = tostring(parse_json(AuthenticationDetails).authenticationStepResultDetail)
| summarize UserCount = dcount(UserPrincipalName), UserList = make_set(UserPrincipalName), AppName = make_set(AppDisplayName), PasswordResult = make_list(PasswordResult) by IPAddress) on IPAddress
| where PasswordResult has "Correct Password"
| where UserCount > 1 // looks for events targeting more than one user.
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPAddress
customDetails:
Score: Score
Baseline: Baseline
UserCount: UserCount
AppName: AppName
PasswordResult: PasswordResult
UserList: UserList
version: 1.0.1
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Juanse
support:
tier: Community
categories:
domains: [ "Identity" ]
| Sentinel Table | Notes |
|---|---|
SigninLogs | Ensure this data connector is enabled |
Scenario: A system administrator is performing a scheduled backup using a script that authenticates with multiple user accounts to verify access.
Filter/Exclusion: Exclude IP addresses associated with scheduled jobs or automation tools like rsync, Ansible, or cron tasks.
Scenario: A security team member is testing multi-factor authentication (MFA) by attempting sign-ins from different user accounts to ensure MFA is functioning correctly.
Filter/Exclusion: Exclude IP addresses used by security teams or tools like Microsoft Authenticator, Google Authenticator, or internal testing environments.
Scenario: A database administrator is running a maintenance job that connects to multiple database users to perform routine checks or updates.
Filter/Exclusion: Exclude IP addresses associated with database maintenance tools like pgAdmin, MySQL Workbench, or internal job scheduling systems like Airflow.
Scenario: A user is using a password manager to auto-fill credentials for multiple accounts during a single sign-on (SSO) session.
Filter/Exclusion: Exclude IP addresses associated with password managers like 1Password, Bitwarden, or internal SSO platforms like Okta or Azure AD.
Scenario: A developer is testing a new application that requires multiple user sign-ins to validate access controls and permissions.
Filter/Exclusion: Exclude IP addresses used by development environments or testing tools like Postman, JMeter, or internal CI/CD pipelines like Jenkins.