Why Password Spraying Persists
Password spraying is classified as T1110.003 in MITRE ATT&CK. The technique is simple: instead of hammering a single account with many passwords (which triggers lockout), an attacker tries one password against every account in the directory. Most enterprise lockout policies trigger after five to ten failed attempts per account. A spray campaign that tries one password every 30 minutes against 500 accounts generates 500 failures across 500 accounts — well under the threshold for any individual user, but a coherent signal if you know what to look for.
The persistence of this technique reflects a structural problem: password reuse. A single credential dump from a prior breach, a seasonal password like Summer2026!, or a company-name variation catches accounts regularly enough that threat actors keep using it. MSTIC data shows that spray campaigns continue to be the dominant credential access technique for nation-state initial access, with Entra ID accounts in cloud-only and hybrid environments as the primary target.
Attack Mechanics
A standard spray campaign follows this pattern:
- Enumerate valid accounts: OSINT (LinkedIn, company website), LDAP enumeration if already on network, or spray-and-check against Entra ID’s sign-in API (which returns different error codes for valid vs. invalid accounts)
- Apply rate limiting: One attempt per account per spray round, with delays of 15-30 minutes between rounds
- Rotate source IPs: Residential proxies, Tor, or cloud VMs to avoid IP-based blocks
- Target common passwords: Season + year, company name variants,
Welcome1,P@ssword1,January2026!
The authentication targets vary:
- On-premises AD: LDAP bind, SMB (NTLMv2), Kerberos AS-REQ with known usernames
- Entra ID: OAuth2 ROPC flow, legacy auth protocols (SMTP AUTH, IMAP, POP3), WS-Federation endpoints
- ADFS: Token endpoint requests
- VPN pre-auth: Pulse Secure, Cisco AnyConnect, GlobalProtect
Telemetry Sources
Effective spray detection requires correlating multiple event sources:
Windows Security Event Log (AD)
- Event ID 4625 — failed logon (Type 3 for network, Type 8 for cleartext NTLM)
- Event ID 4771 — Kerberos pre-auth failure (Status 0x18 = bad password)
- Event ID 4776 — NTLM credential validation failure
- Event ID 4648 — logon with explicit credentials
Entra ID / Azure AD Sign-in Logs
ResultType50126 — invalid credentialsResultType50053 — account lockedResultType50055 — expired passwordAuthenticationDetails+ClientAppUsedfor legacy auth identification
Network / Firewall
- High-volume authentication requests from a single external IP to port 389 (LDAP), 636 (LDAPS), 445 (SMB), 88 (Kerberos)
Sigma Detection Rules
Rule 1: Distributed Authentication Failures Across Many Accounts (Spray Pattern)
title: Active Directory Password Spray - Distributed Account Failures
id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
status: test
description: Detects a spray pattern where many distinct accounts fail authentication within a short window from the same source
references:
- https://attack.mitre.org/techniques/T1110/003/
author: SOC Analyst Hub
date: 2026/07/17
tags:
- attack.credential_access
- attack.t1110.003
logsource:
product: windows
service: security
detection:
selection:
EventID: 4625
LogonType: 3
SubStatus: '0xc000006a' # Bad password (not account not found)
condition: selection | count(TargetUserName) by IpAddress > 20
timeframe: 30m
falsepositives:
- Misconfigured service accounts cycling through accounts
- Batch logon testing in dev environments
level: high
Rule 2: Kerberos Pre-Authentication Spray (AS-REQ Failures)
title: Kerberos Password Spray via AS-REQ Pre-Auth Failures
id: b2c3d4e5-f6a7-8901-bcde-f12345678901
status: test
description: Detects spray attacks via Kerberos by correlating AS-REQ pre-auth failures across many accounts
references:
- https://attack.mitre.org/techniques/T1110/003/
author: SOC Analyst Hub
date: 2026/07/17
tags:
- attack.credential_access
- attack.t1110.003
logsource:
product: windows
service: security
detection:
selection:
EventID: 4771
Status: '0x18' # KDC_ERR_PREAUTH_FAILED
filter_service_accounts:
TargetUserName|endswith: '$' # Exclude machine accounts
condition: selection and not filter_service_accounts | count(TargetUserName) by IpAddress > 15
timeframe: 30m
falsepositives:
- Users experiencing genuine password issues
- Password migration events
level: high
Rule 3: Low-and-Slow Spray (Evading Burst Thresholds)
title: Low-and-Slow Password Spray - Extended Timeframe
id: c3d4e5f6-a7b8-9012-cdef-123456789012
status: test
description: Detects spray campaigns that operate slowly to avoid burst detection — few failures per account but many unique accounts over hours
references:
- https://attack.mitre.org/techniques/T1110/003/
author: SOC Analyst Hub
date: 2026/07/17
tags:
- attack.credential_access
- attack.t1110.003
logsource:
product: windows
service: security
detection:
selection:
EventID: 4625
LogonType:
- 3
- 8
condition: selection | count(TargetUserName) by IpAddress > 50
timeframe: 4h
falsepositives:
- Load balancers with shared external IP for multiple users
- Large NAT environments where many users share one external IP
level: medium
Rule 4: Legacy Authentication Spray (Entra ID)
title: Entra ID Legacy Auth Protocol Spray
id: d4e5f6a7-b8c9-0123-defa-234567890123
status: test
description: Detects password spray against Entra ID via legacy authentication protocols which bypass MFA
references:
- https://attack.mitre.org/techniques/T1110/003/
- https://attack.mitre.org/techniques/T1078/004/
author: SOC Analyst Hub
date: 2026/07/17
tags:
- attack.credential_access
- attack.t1110.003
- attack.initial_access
- attack.t1078.004
logsource:
product: azure
service: signinlogs
detection:
selection:
ResultType: 50126 # Invalid credentials
ClientAppUsed:
- 'Exchange ActiveSync'
- 'Exchange Online PowerShell'
- 'Exchange Web Services'
- 'IMAP4'
- 'MAPI Over HTTP'
- 'POP3'
- 'Reporting Web Services'
- 'Other clients'
condition: selection | count(UserPrincipalName) by IPAddress > 10
timeframe: 30m
falsepositives:
- Shared IP environments
- Legacy applications with authentication configuration issues
level: high
KQL Detection Queries (Microsoft Sentinel)
Query 1: Spray Pattern from Single Source IP
// Detect password spray: many distinct failed logons from one IP
SecurityEvent
| where EventID == 4625
| where LogonType == 3
| where SubStatus == "0xc000006a" // Bad password
| where TargetUserName !endswith "$" // Exclude machine accounts
| summarize
FailedAccounts = dcount(TargetUserName),
FailedAttempts = count(),
AccountList = make_set(TargetUserName, 20),
FirstSeen = min(TimeGenerated),
LastSeen = max(TimeGenerated)
by IpAddress, bin(TimeGenerated, 30m)
| where FailedAccounts > 20
| extend SprayIndicator = true
| project TimeGenerated, IpAddress, FailedAccounts, FailedAttempts, AccountList, FirstSeen, LastSeen
| sort by FailedAccounts desc
Query 2: Entra ID Sign-in Spray with Success Correlation
// Correlate Entra ID spray failures with any subsequent successes (compromise indicator)
let SprayIPs =
SigninLogs
| where ResultType == 50126 // Invalid credentials
| where TimeGenerated > ago(2h)
| summarize FailedAccounts = dcount(UserPrincipalName) by IPAddress
| where FailedAccounts > 10
| project IPAddress;
SigninLogs
| where ResultType == 0 // Successful sign-in
| where IPAddress in (SprayIPs)
| where TimeGenerated > ago(2h)
| project
TimeGenerated,
UserPrincipalName,
IPAddress,
AppDisplayName,
ClientAppUsed,
AuthenticationRequirement,
ConditionalAccessStatus,
DeviceDetail,
Location
Query 3: Cross-Protocol Spray Correlation
// Detect spray across multiple protocols from same IP
let TimeWindow = 1h;
let SprayThreshold = 15;
union
(
SecurityEvent
| where EventID in (4625, 4771)
| where TimeGenerated > ago(TimeWindow)
| extend Protocol = case(EventID == 4771, "Kerberos", "NTLM/SMB")
| project TimeGenerated, IpAddress, TargetUserName, Protocol
),
(
SigninLogs
| where ResultType in (50126, 50053)
| where TimeGenerated > ago(TimeWindow)
| extend Protocol = "EntraID", IpAddress = IPAddress, TargetUserName = UserPrincipalName
| project TimeGenerated, IpAddress, TargetUserName, Protocol
)
| summarize
UniqueAccounts = dcount(TargetUserName),
Protocols = make_set(Protocol),
Attempts = count()
by IpAddress
| where UniqueAccounts > SprayThreshold
| extend MultiProtocolSpray = array_length(Protocols) > 1
Tuning and Reducing False Positives
Common false positive sources:
Large NAT environments: Many users sharing a single public IP will generate correlated failures that look like spraying. Maintain an allowlist of known corporate egress IPs and adjust thresholds accordingly. Tune the KQL queries to exclude these IP ranges.
Password expiration waves: When organisational password policies expire a large cohort at the same time, genuine users failing to authenticate with expired credentials can trigger spray alerts. Correlate with EventID 4625 SubStatus 0xc0000071 (expired password) separately.
Service account misconfigurations: A service account with a wrong password configured in dozens of places generates exactly the failure distribution of a spray. These are identifiable because they fail with the same username repeatedly rather than rotating through accounts.
Load balancers and proxies: Authenticate traffic aggregated behind a proxy will present a single source IP for many legitimate users.
Tuning recommendations:
- Baseline your environment’s failure rate per IP over 30-day rolling windows
- Set thresholds at 3x the 95th percentile of your normal failure count
- Monitor for threshold tuning manipulation (attackers who have baseline knowledge will spray just below detection thresholds)
Response Playbook
On spray alert:
- Identify source IP(s): Geolocate, check against known VPN/proxy/Tor exit node lists, check prior occurrence in threat intel feeds
- Identify targeted accounts: Which usernames were targeted? Are these real accounts? Real users? Executive accounts?
- Check for successes: Did any targeted account authenticate successfully after failed sprays? This is your highest-priority finding
- Block at perimeter: Null-route or block the source IP at the firewall. For distributed sprays, consider blocking ASNs associated with residential proxy providers
- Force password resets: For any accounts that appear targeted, consider forcing a password reset and requiring re-authentication
- Check for legacy auth: If spray was via legacy protocols, verify those protocols are disabled in Entra ID conditional access
ATT&CK Mapping
| Technique | Sub-technique | Tactic |
|---|---|---|
| T1110 | T1110.003 (Password Spraying) | Credential Access (TA0006) |
| T1078 | T1078.002 (Domain Accounts) | Initial Access (TA0001), Defense Evasion (TA0005) |
| T1110 | T1110.001 (Password Guessing) | Credential Access (TA0006) |
Password spraying remains effective because of password reuse, weak policy enforcement, and lack of detection coverage on Kerberos and legacy auth protocols. The detection rules above provide coverage across the primary spray vectors in AD and Entra ID environments.