Adversaries may be attempting to brute-force credentials by generating a high volume of failed sign-in events. SOC teams should proactively hunt for this behavior in Azure Sentinel to detect potential credential compromise attempts early.
KQL Query
let starttime = todatetime('{{StartTimeISO}}');
let endtime = todatetime('{{EndTimeISO}}');
let auditLookback = starttime - 14d;
let ts_data = (SigninLogs
| where TimeGenerated between (auditLookback..endtime)
| where ResultType != 0
| make-series count() on TimeGenerated step 1h by UserPrincipalName
| extend series_decompose(count_)
| extend NoLogons = count_);
let TimeSeriesAlerts=ts_data
| extend (anomalies, score, baseline) = series_decompose_anomalies(count_, 1.5, -1, 'linefit',0, 'ctukey', 0.7)
| mv-expand NoLogons to typeof(double), TimeGenerated to typeof(datetime), anomalies to typeof(double),score to typeof(double), baseline to typeof(long)
| where anomalies > 0
| project UserPrincipalName, TimeGenerated, NoLogons, baseline, anomalies, score;
TimeSeriesAlerts
| join kind=inner (
SigninLogs
| where TimeGenerated between (auditLookback..endtime)
| summarize ResultTypeCount=count(),ResultTypes=make_set(ResultType), Locations=make_set(Location), Apps=make_set(AppDisplayName), Ips=make_set( IPAddress) by UserPrincipalName, bin(TimeGenerated, 1h)
) on UserPrincipalName, TimeGenerated
| summarize AnomolyTimes = make_set(TimeGenerated), Ips = make_set(Ips), Apps = make_set(Apps), sum(anomalies), Locations=make_set(Locations) by UserPrincipalName
| sort by sum_anomalies desc
| extend timestamp = tostring(AnomolyTimes[0]), AccountCustomEntity = UserPrincipalName
id: 51f4faf9-c3b1-4e9f-9c90-5d6afd191552
name: Spike in failed sign-in events
description: |
'Identifies spikes in failed sign-in events based on the volume of failed sign-in events over time. Use to identify patterns of suspicious behavior such as unusually high failed sign-in attempts from certain users.
Ref : https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-user-accounts'
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- SigninLogs
tactics:
- InitialAccess
relevantTechniques:
- T1078.004
query: |
let starttime = todatetime('{{StartTimeISO}}');
let endtime = todatetime('{{EndTimeISO}}');
let auditLookback = starttime - 14d;
let ts_data = (SigninLogs
| where TimeGenerated between (auditLookback..endtime)
| where ResultType != 0
| make-series count() on TimeGenerated step 1h by UserPrincipalName
| extend series_decompose(count_)
| extend NoLogons = count_);
let TimeSeriesAlerts=ts_data
| extend (anomalies, score, baseline) = series_decompose_anomalies(count_, 1.5, -1, 'linefit',0, 'ctukey', 0.7)
| mv-expand NoLogons to typeof(double), TimeGenerated to typeof(datetime), anomalies to typeof(double),score to typeof(double), baseline to typeof(long)
| where anomalies > 0
| project UserPrincipalName, TimeGenerated, NoLogons, baseline, anomalies, score;
TimeSeriesAlerts
| join kind=inner (
SigninLogs
| where TimeGenerated between (auditLookback..endtime)
| summarize ResultTypeCount=count(),ResultTypes=make_set(ResultType), Locations=make_set(Location), Apps=make_set(AppDisplayName), Ips=make_set( IPAddress) by UserPrincipalName, bin(TimeGenerated, 1h)
) on UserPrincipalName, TimeGenerated
| summarize AnomolyTimes = make_set(TimeGenerated), Ips = make_set(Ips), Apps = make_set(Apps), sum(anomalies), Locations=make_set(Locations) by UserPrincipalName
| sort by sum_anomalies desc
| extend timestamp = tostring(AnomolyTimes[0]), AccountCustomEntity = UserPrincipalName
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: UserPrincipalName
| Sentinel Table | Notes |
|---|---|
SigninLogs | Ensure this data connector is enabled |
Scenario: Scheduled System Maintenance or Patching
Description: A legitimate scheduled job runs during off-hours and generates a spike in failed sign-in events due to automated scripts or maintenance tools attempting to authenticate.
Filter/Exclusion: Exclude events where the source IP is associated with known internal maintenance tools (e.g., Ansible, Puppet, or Jenkins) or where the user agent matches a known automation tool.
Example Filter: user_agent contains "Ansible" or source_ip in [internal_maintenance_ips]
Scenario: Failed Sign-In Attempts from a Misconfigured Service Account
Description: A service account with incorrect credentials is periodically attempted to authenticate by a legitimate system (e.g., a backup tool or monitoring service), leading to failed sign-in events.
Filter/Exclusion: Exclude events where the username matches a known service account or where the source IP is from a trusted internal system (e.g., 10.0.0.0/8).
Example Filter: username contains "svc-" or source_ip in [internal_service_account_ips]
Scenario: User Testing or Password Reset Attempts
Description: A user is testing their password or attempting to reset it, leading to multiple failed sign-in attempts within a short period.
Filter/Exclusion: Exclude events where the username is associated with a user who has recently initiated a password reset or where the event occurred during a known password reset window.
Example Filter: username in [users_with_password_reset_requests] or event_time between [password_reset_window_start] and [password_reset_window_end]
Scenario: Network Scanning or Port Scanning Activity
Description: A legitimate network scan or port scan tool (e.g., Nmap, Masscan) is used to test network security, resulting in failed sign-in attempts from multiple IP addresses.