Password Spraying
Adversaries may use a single or small list of commonly used passwords against many different accounts to attempt to acquire valid account credentials. Password spraying uses one password (e.g. 'Password01'), or a small list of commonly used passwords, that may match the complexity policy of the domain. Logins are attempted with that password against many different accounts on a network to avoid account lockouts that would normally occur when brute forcing a single account with many passwords. This technique is deliberately throttled to avoid triggering per-account lockout thresholds — the defining characteristic that distinguishes spraying from brute force (T1110.001). Adversaries including APT28, APT29, HAFNIUM, Storm-0940, Chimera, and APT33 have used this technique at scale against OWA, Microsoft 365, VPN portals, SSH, RDP, SMB, and LDAP. Slow-spray variants (approximately 4 attempts per account per hour) are specifically designed to evade detection thresholds, and Kerberos-based spraying is used to avoid generating the high-visibility Event ID 4625 typically alerted on.
let SprayAccountThreshold = 10;
let BruteForceMaxPerAccount = 5;
let LookbackWindow = 24h;
let SprayWindow = 30m;
// Azure AD / Entra ID password spray detection via SigninLogs
let AzureADSpray = SigninLogs
| where TimeGenerated > ago(LookbackWindow)
| where ResultType != "0"
| where ResultType in (
"50126", // Invalid username or password
"50055", // Expired password
"50056", // Null password in store
"50064", // Credentials expired
"50053", // Account locked out
"50057", // User account disabled
"50074", // Strong auth required but not provided
"50059", // Tenant not found
"50076" // MFA required but not provided
)
| where isnotempty(IPAddress) and IPAddress !in ("127.0.0.1", "::1")
| summarize
FailureCount = count(),
DistinctAccounts = dcount(UserPrincipalName),
TargetAccounts = make_set(UserPrincipalName, 30),
FirstSeen = min(TimeGenerated),
LastSeen = max(TimeGenerated),
TargetApps = make_set(AppDisplayName, 10),
UserAgents = make_set(UserAgent, 5)
by IPAddress, bin(TimeGenerated, SprayWindow)
| where DistinctAccounts >= SprayAccountThreshold
| extend AvgFailuresPerAccount = round(toreal(FailureCount) / toreal(DistinctAccounts), 2)
| where AvgFailuresPerAccount <= BruteForceMaxPerAccount
| extend DetectionSource = "AzureAD_SigninLogs";
// On-premises AD password spray detection via Security Event 4625
let OnPremADSpray = SecurityEvent
| where TimeGenerated > ago(LookbackWindow)
| where EventID == 4625
| where LogonType in (3, 10) // Network, RemoteInteractive
| where IpAddress !in ("-", "", "::1", "127.0.0.1")
| summarize
FailureCount = count(),
DistinctAccounts = dcount(TargetUserName),
TargetAccounts = make_set(TargetUserName, 30),
FirstSeen = min(TimeGenerated),
LastSeen = max(TimeGenerated),
TargetApps = make_set(WorkstationName, 10),
UserAgents = dynamic([])
by IpAddress, bin(TimeGenerated, SprayWindow)
| where DistinctAccounts >= SprayAccountThreshold
| extend AvgFailuresPerAccount = round(toreal(FailureCount) / toreal(DistinctAccounts), 2)
| where AvgFailuresPerAccount <= BruteForceMaxPerAccount
| extend DetectionSource = "OnPremAD_SecurityEvent"
| project-rename IPAddress = IpAddress;
union AzureADSpray, OnPremADSpray
| sort by DistinctAccounts desc Data Sources
Required Tables
False Positives
- Misconfigured service accounts using stale credentials that authenticate against multiple systems simultaneously during a configuration failure event
- Authorized penetration testing or red team exercises targeting multiple accounts with common passwords from a designated test IP
- ADFS or federated identity proxy services whose single IP proxies authentication for all federated users — these IPs will appear as the source for all federation failures
- Vulnerability scanners (Tenable, Qualys, Rapid7) performing authenticated scans with cycling credentials across multiple hosts
- Password synchronization failures during Active Directory migrations or forest trust establishment generating bulk 4625 events from the migration service account IP
References (13)
- https://attack.mitre.org/techniques/T1110/003/
- https://www.trimarcsecurity.com/single-post/2018/05/06/Trimarc-Research-Detecting-Password-Spraying-with-Security-Event-Auditing
- https://www.microsoft.com/en-us/security/blog/2024/10/31/chinese-threat-actor-storm-0940-uses-credentials-from-password-spray-attacks-from-a-covert-network/
- http://www.blackhillsinfosec.com/?p=4645
- https://www.us-cert.gov/ncas/alerts/TA18-086A
- https://github.com/dafthack/MSOLSpray
- https://github.com/dafthack/MailSniper
- https://github.com/ropnop/kerbrute
- https://github.com/GhostPack/Rubeus
- https://learn.microsoft.com/en-us/azure/active-directory/reports-monitoring/concept-sign-ins
- https://learn.microsoft.com/en-us/azure/active-directory/reports-monitoring/reference-sign-ins-error-codes
- https://www.microsoft.com/en-us/security/blog/2021/06/25/nobelium-attacking-more-than-150-organizations/
- https://github.com/byt3bl33d3r/CrackMapExec
Unlock Pro Content
Get the full detection package for T1110.003 including response playbook, investigation guide, and atomic red team tests.