Detect Password Spray Attacks in Microsoft Entra ID Using KQL

Learn how to detect distributed password spray attacks targeting Microsoft Entra ID using advanced KQL queries, threshold aggregation, and IP failure rate analysis.

Dikshant Lather
2 min read ·
Detect Password Spray Attacks in Microsoft Entra ID Using KQL

Detect Password Spray Attacks in Microsoft Entra ID Using KQL

Password spray attacks remain one of the most persistent identity threats targeting enterprise tenants. Unlike standard brute-force attacks that bombard a single account with hundreds of passwords, password spraying tests a single common password across hundreds of different user accounts to circumvent standard account lockout policies.

In this guide, we break down how to craft an enterprise-grade Kusto Query Language (KQL) detection rule in Microsoft Sentinel utilizing the SigninLogs table.


Prerequisites & Log Ingestion

To execute this detection, ensure your Microsoft Sentinel workspace is actively collecting:

  • Microsoft Entra ID (Azure AD) SigninLogs (Interactive user sign-ins)
  • Optional: AADNonInteractiveUserSignInLogs for service and automation auditing

Detection KQL Query

// Detect Distributed Password Spraying across Multiple Accounts from a Single IP or Subnet
let timeRange = 1h;
let failureThreshold = 10;
let accountThreshold = 8;
SigninLogs
| where TimeGenerated >= ago(timeRange)
// Filter for Bad Password or Account Disabled error codes
| where ResultType in (50126, 50053) // 50126: Invalid password, 50053: Account locked/disabled
| summarize 
    FailedAccounts = make_set(UserPrincipalName, 50),
    TotalFailures = count(),
    UniqueAccountCount = dcount(UserPrincipalName),
    FirstSeen = min(TimeGenerated),
    LastSeen = max(TimeGenerated)
    by IPAddress, Location, AppDisplayName
| where UniqueAccountCount >= accountThreshold and TotalFailures >= failureThreshold
| extend AttackDurationMinutes = datetime_diff('minute', LastSeen, FirstSeen)
| project IPAddress, Location, AppDisplayName, UniqueAccountCount, TotalFailures, AttackDurationMinutes, FailedAccounts
| order by UniqueAccountCount desc

Query Logic Breakdown

  1. Error Code Filtering (ResultType): We specifically isolate AADSTS50126 (Invalid username or password) and AADSTS50053 (Account locked).
  2. Aggregation by Origin IP: Using summarize ... by IPAddress, we isolate attacking hosts regardless of which usernames they probe.
  3. Distinct Account Threshold (dcount): A genuine user forgetting their password hits 1 account multiple times. An attacker hits >= 8 distinct accounts from one source.
  4. Duration Calculation: Calculates whether the attack was an automated burst or a slow-and-low campaign.

Tuning False Positives

  • Corporate Egress IPs: If your organization routes office traffic through shared proxy egress points, filter out known NAT IPs using an allowed CIDR watch list.
  • Misconfigured Internal Apps: Legacy internal apps attempting service binds with expired service account passwords can mimic spray behavior. Inspect AppDisplayName before triggering containment playbooks.

MITRE ATT&CK Mapping

  • Tactic: Credential Access (TA0006)
  • Technique: Brute Force: Password Spraying (T1110.003)
Dikshant Lather
Written by

Dikshant Lather

Cyber Security & AI Architect

Responses (0)

Join the technical conversation or share implementation thoughts.

What are your thoughts?

Sign in to join the technical discussion or share feedback.

There are currently no responses for this story. Be the first to respond.