Password Spraying: How This Low-and-Slow Attack Bypasses Account Lockouts

Password spraying is an elegant attack that sidesteps account lockout policies by trying a single common password against many accounts, rather than many passwords against one account. It’s one of the most common ways attackers gain initial access to corporate environments — and it works far more often than it should.

Password Spraying vs Brute Force

# Brute Force (gets caught):
# Same account, many passwords:
admin: password1 -> FAIL
admin: password2 -> FAIL (lockout at attempt 5)
admin: password3 -> LOCKOUT TRIGGERED

# Password Spraying (avoids lockout):
# One password, many accounts:
alice: Summer2024! -> FAIL
bob: Summer2024!   -> FAIL
charlie: Summer2024! -> SUCCESS!
dave: Summer2024!  -> FAIL
# Only 1 attempt per account = never triggers lockout

# Why common passwords work:
# Analysis of leaked corporate credentials shows:
# ~5% of users choose seasonal passwords: Winter2024!, Spring2025!
# ~3% use company name variations: Contoso2024!, Company123!
# ~2% use "password" + year: Password2024, Password2025!

Real-World Example

In 2020, the US CISA and NSA issued an advisory about Russian APT28 (Fancy Bear) conducting password spray attacks against Office 365 and on-premises email systems. In 2021, Iranian threat actors used the same technique against US critical infrastructure. Password spraying is a state-sponsored technique that works against even sophisticated organizations.

Tools Used (For Awareness)

# Password spraying tools attackers use:
# MSOLSpray (Office 365): github.com/dafthack/MSOLSpray
# Ruler (Exchange): github.com/sensepost/ruler
# Spray (SMB, LDAP, WinRM): github.com/Greenwolf/Spray

# Detection: These tools hit authentication endpoints at regular intervals
# Network signature: Many auth requests from single IP with one-per-account cadence

Detection

# Detect password spray attempts in Azure AD / Entra ID logs:

# Azure Sentinel KQL query:
SigninLogs
| where TimeGenerated > ago(1h)
| where ResultType != "0"  // Failed logins only
| summarize 
    FailedLogins = count(),
    UniqueUsers = dcount(UserPrincipalName),
    UniqueIPs = dcount(IPAddress)
  by bin(TimeGenerated, 5m), IPAddress
| where UniqueUsers > 10 and FailedLogins < UniqueUsers * 2
// High unique users, low attempts per user = password spray pattern

# Windows Event ID 4625 (Failed Logon) analysis:
# Many unique accounts, similar timestamps, same source IP

# Alert threshold:
# More than 10 unique accounts with failed auth in 10 minutes from same IP

Prevention

# 1. Multi-Factor Authentication (biggest impact)
# Even if password is sprayed successfully, attacker still needs 2nd factor

# 2. Conditional Access — block legacy authentication protocols:
# Legacy auth (SMTP, POP3, IMAP) doesn't support MFA!
# Azure AD Conditional Access Policy:
# Condition: Client apps = "Exchange ActiveSync clients" and "Other clients"
# Grant: Block access
# This forces modern authentication which supports MFA

# 3. Smart Lockout (Azure AD):
# Different from classic lockout - learns user behavior
# az ad sp list --query "[].{name:displayName}" > /dev/null
# Configure in: Entra ID > Authentication methods > Password protection

# 4. Password Protection / Banned Password Lists:
# Azure AD Password Protection bans common + company-specific passwords
# Install agent on on-premises AD to enforce in hybrid environments

# 5. Audit your own organization:
# Run Spray against yourself with authorization!
./spray.py -U users.txt -P Summer2024! --target 192.168.1.10 --proto smb
# Any hits? Those users need immediate password changes + security training

Wrap Up

Password spraying works because people are predictable with passwords. The defense is equally simple: enforce MFA on everything, block legacy authentication, and deploy Azure AD Password Protection to prevent seasonal/company-name passwords. Running a password spray test against your own organization (with permission) is one of the most valuable security exercises you can do.