How to Detect Password Spray Attacks Against Active Directory

Retool's new app builder is where AI-generated code ships safely
Building apps with AI is easy. Getting them to production safely is another story.
Password spray is the most common cloud identity attack technique because it works: enterprise users frequently choose passwords based on company name + year + special character (CompanyName2024!), and spray tools test these patterns across the entire tenant in minutes. Microsoft reports password spray as the technique behind the majority of compromised Microsoft 365 accounts.
The attack is designed to be invisible to standard monitoring: each individual account sees only one failed attempt, well below lockout thresholds. Detection requires aggregate analysis across accounts: looking for a single source IP failing against many different accounts within a time window.
Detection: On-Premises Active Directory (Event 4625)
// Sentinel KQL: Detect password spray via unique failed account count per source IP
// Focus: many DIFFERENT accounts failing from one IP (spray signature)
// vs. many failures against ONE account (brute force signature)
SecurityEvent
| where TimeGenerated > ago(1h)
| where EventID == 4625 // Failed logon
// Filter to relevant logon types (network and interactive):
| where LogonType in (3, 10) // 3 = network, 10 = remote interactive (RDP)
// Filter to password-wrong failures (not unknown user):
| where SubStatus == "0xC000006A" // Wrong password for valid user
or SubStatus == "0xC0000064" // Non-existent username (include to catch user enum)
| summarize
FailedAccounts = dcount(TargetUserName),
AccountList = make_set(TargetUserName, 30),
TotalFailures = count()
by IpAddress, bin(TimeGenerated, 30m)
// Spray threshold: 20+ unique accounts failed from one IP in 30 minutes:
| where FailedAccounts >= 20
| where TotalFailures < FailedAccounts * 3 // Spray: few retries per account
| project TimeGenerated, IpAddress, FailedAccounts, TotalFailures,
AccountList
| order by FailedAccounts desc
On-premises spray from distributed IPs (botnet spray):
// Slower distributed spray: fewer accounts per IP, but same accounts hit from many IPs
SecurityEvent
| where TimeGenerated > ago(24h)
| where EventID == 4625
| where SubStatus == "0xC000006A" // Wrong password
// Count: how many different source IPs tried each account
| summarize
SourceIPs = dcount(IpAddress),
IPList = make_set(IpAddress, 20),
FailureCount = count()
by TargetUserName, bin(TimeGenerated, 1h)
// Alert: same account tested from many different IPs
| where SourceIPs >= 5 and FailureCount >= 5
// This catches spray-from-botnet where each node sprays a small subnet
| order by SourceIPs desc
On-premises AD: enable verbose failed logon detail:
GPO: Computer Configuration > Windows Settings > Security Settings >
Advanced Audit Policy Configuration > Logon/Logoff >
"Audit Logon": Failure = Enabled
"Audit Account Logon": Failure = Enabled (on DCs, captures NTLM/Kerberos failures)
Event 4625 on member servers = NTLM attempt
Event 4771 on DCs = Kerberos pre-auth failure (separate spray detection needed)
Detection: Microsoft Entra ID / Microsoft 365
// Sentinel: Entra ID sign-in log spray detection
SigninLogs
| where TimeGenerated > ago(1h)
| where ResultType != "0" // Failed sign-ins
// Focus on password-wrong failures (not locked out, not MFA fail):
| where ResultDescription !has "locked" and ResultDescription !has "MFA"
| summarize
FailedUsers = dcount(UserPrincipalName),
UserList = make_set(UserPrincipalName, 30),
TotalAttempts = count()
by IPAddress, bin(TimeGenerated, 30m)
| where FailedUsers >= 15 // 15+ unique users failed from one IP = spray
| project TimeGenerated, IPAddress, FailedUsers, TotalAttempts, UserList
Entra ID Identity Protection built-in spray detection:
Microsoft Entra ID Identity Protection (P2) includes a Password Spray risk detection that fires automatically when its machine learning model detects spray patterns.
Entra admin center > Protection > Identity Protection > Risk Detections
Detection type: "Password spray" (risk level: High)
To alert on it in Sentinel:
AADRiskyUsers
| where RiskEventTypes has "passwordSpray"
| project UserPrincipalName, RiskLevel, RiskState, RiskLastUpdatedDateTime
Or via the unified sign-in risk:
SigninLogs
| where RiskEventTypes_s has "passwordSpray"
| summarize count() by UserPrincipalName, IPAddress
Detect spray followed by successful logon (compromise confirmation):
// Alert: user who appears in failed spray list then successfully authenticates
let SprayIPs = SigninLogs
| where TimeGenerated > ago(2h)
| where ResultType != "0"
| summarize FailedUsers = dcount(UserPrincipalName) by IPAddress
| where FailedUsers >= 15
| project IPAddress;
SigninLogs
| where TimeGenerated > ago(2h)
| where ResultType == "0" // Successful
| where IPAddress in (SprayIPs)
// Successful logon from an IP that was spraying = spray succeeded:
| project TimeGenerated, UserPrincipalName, IPAddress, Location, AppDisplayName
Briefings like this, every morning before 9am.
Threat intel, active CVEs, and campaign alerts, distilled for practitioners. 50,000+ subscribers. No noise.
Mitigations: Constrain Spray Blast Radius
Entra ID Smart Lockout (already on by default: verify settings):
Entra admin center > Protection > Authentication methods > Password protection
Lockout threshold: 10 (default): lower to 5 for sensitive tenants
Lockout duration (seconds): 60 (default): increase to 300 for higher friction
Smart Lockout tracks per-credential-hash, not per-IP:
- Legitimate users from multiple locations do NOT get locked out by attacker spray
- Attacker IPs DO get blocked after threshold even if rotating users
Entra ID Password Protection (blocks common passwords):
Entra admin center > Protection > Authentication methods > Password protection
Enforce custom banned passwords: Enabled
Add company-specific terms:
- [CompanyName], [CompanyAcronym]
- Common seasons: Spring, Summer, Fall, Winter
- Common years: 2023, 2024, 2025
This blocks the most common spray targets:
- CompanyName2024!
- Summer2024@
- Password123! (Microsoft global banned list already catches this)
For on-premises AD:
Deploy Microsoft Entra Password Protection for Windows Server AD
(Agent on DCs enforces the same banned password list for on-prem accounts)
Conditional Access: block spray from known bad countries:
Conditional Access > Named Locations > Countries location
Exclude countries where your org has no users
Policy: Block sign-in from outside approved countries
Target: All users
Conditions: Locations = exclude approved countries
Grant: Block access
This eliminates overseas spray infrastructure as an attack vector
Enable MFA for all accounts (eliminates spray as a standalone attack):
Conditional Access > New Policy
Users: All users
Cloud apps: All cloud apps
Grant: Require multi-factor authentication
With MFA enforced, a successful spray attack (correct password found) still
requires defeating MFA before the account is compromised: spray becomes
only the first step of a longer attack chain
The bottom line
Password spray detection requires aggregate analysis: count distinct TargetUserName values (on-prem: Event 4625 with SubStatus 0xC000006A) or distinct UserPrincipalName values (cloud: SigninLogs with ResultType != 0) per source IP in a rolling window: alert when 15-20+ unique accounts fail from a single IP within 30 minutes. In Entra ID, enable Identity Protection P2 for built-in spray detection (risk event type: passwordSpray). Constrain spray damage with Entra Password Protection (banned company-specific passwords), Smart Lockout, and Conditional Access blocking logins from non-operating countries. MFA enforcement on all accounts prevents spray from being a complete compromise: correct password found still requires a second factor.
Frequently asked questions
What is the difference between password spray and brute force attacks?
Brute force attacks target a single account with many different passwords: easily detected by account lockout and by high Event 4625 counts for one TargetUserName. Password spray tests one (or a few) passwords against many different accounts: avoiding lockout by staying below the threshold per account. Detection requires counting distinct TargetUserName values per source IP rather than total failures per account. Both appear as Event 4625 on Windows DCs, but spray generates high unique account counts, while brute force generates high total failure counts per account.
How does Entra ID Smart Lockout protect against password spray?
Smart Lockout in Entra ID tracks failed attempts per credential hash rather than per IP address. This means legitimate users authenticating from multiple locations or different IPs are not affected when an attacker sprays against their account from a different IP. The attacker's IP gets incrementally blocked after the lockout threshold. Smart Lockout is enabled by default; adjust the threshold (default: 10) and lockout duration (default: 60 seconds) in Entra admin center > Protection > Authentication Methods > Password Protection.
How do I detect a password spray attack against Active Directory?
Password spray detection requires different thresholds than brute force. Brute force: many failures against one account. Password spray: few failures (1-3) against many accounts from a single source. SIEM detection query: group Event ID 4625 (logon failure) by SubjectUserName (or IpAddress for network logons), count distinct TargetUserName values per source in a 1-hour window, and alert when a single source hits more than 20 distinct accounts with failures. Also check: failed authentications for accounts with names following a predictable corporate pattern (First.Last@company.com) from a single IP; and a spike in overall failed logins across the domain compared to a 7-day baseline.
How do I configure Active Directory lockout policies to balance security and availability?
Account lockout policy recommendations from Microsoft and NIST: Account Lockout Threshold: 5-10 invalid attempts (5 blocks spray attacks quickly; 10 reduces helpdesk calls for clumsy typists). Observation Window: 15 minutes (the window during which failures accumulate toward the threshold). Account Lockout Duration: 15-30 minutes with administrator unlock enabled (auto-unlock reduces helpdesk burden; a 15-minute wait frustrates attackers while being manageable for users). For Fine-Grained Password Policies (available in AD DS 2008+): apply stricter policies to privileged accounts (Domain Admins, service accounts) with a threshold of 3 failures, and more lenient policies for standard user accounts. Fine-grained policies override the default domain policy for specific groups.
What is Entra ID Password Protection and how does it prevent common passwords?
Entra ID Password Protection maintains a global banned password list (updated based on Azure AD's analysis of breach password datasets) and an optional custom banned password list. When users set or change passwords, the service checks against both lists: common passwords like 'Password1!' and 'Summer2023' are blocked even with complexity requirements technically satisfied. For on-premises Active Directory, deploy the Entra ID Password Protection DC Agent (free) on each domain controller: it downloads the banned password list and enforces it during AD password changes, extending cloud-based protection to hybrid and on-premises environments. Enable in Entra admin center > Security > Authentication Methods > Password Protection.
How do you detect password spray originating from a compromised internal machine rather than an external attacker IP?
Internal spray detection requires adjusted thresholds because the source IP is trusted by default. On-premises Event 4625 is the primary signal: filter to LogonType 3 (network logon) from internal source IPs, then count distinct TargetUserName values per source IP in a 30-minute window. Internal machines should fail authentication against very few accounts under normal conditions -- more than 5-10 unique accounts failing from a single internal workstation IP is anomalous and warrants immediate investigation. For Kerberos-based internal spray: monitor Event ID 4771 (Kerberos pre-authentication failed) on domain controllers with Error Code 0x18 (wrong password), since spray tools targeting Kerberos generate 4771 rather than 4625. Combine the detection with process-level context: if the spray originates from an interactive session on a workstation, correlate the source IP back to process creation events (Sysmon Event 1) to identify which process is generating the authentication attempts. Internal spray from a compromised machine is typically more targeted than external spray and uses a shorter account list, so lower your unique-account threshold for internal source IPs.
Sources & references
Free resources
Critical CVE Reference Card 2025–2026
25 actively exploited vulnerabilities with CVSS scores, exploit status, and patch availability. Print it, pin it, share it with your SOC team.
Ransomware Incident Response Playbook
Step-by-step 24-hour IR checklist covering detection, containment, eradication, and recovery. Built for SOC teams, IR leads, and CISOs.
Get threat intel before your inbox does.
50,000+ security professionals read Decryption Digest for early warnings on zero-days, ransomware, and nation-state campaigns. Free, daily, no spam.
Unsubscribe anytime. We never sell your data.

Founder & Cybersecurity Evangelist, Decryption Digest
Cybersecurity professional with expertise in threat intelligence, vulnerability research, and enterprise security. Covers zero-days, ransomware, and nation-state operations for 50,000+ security professionals every morning.
Win a $2,495 Black Hat pass.
Full-access to Black Hat USA 2026 in Las Vegas. Subscribe free to enter.
