Lockout-Aware Password Spraying
Password spraying — trying one password against many accounts — is one of the highest-value moves in an internal Active Directory engagement. It’s also one of the fastest ways to torpedo an engagement: spray too aggressively and you lock out half the domain, trigger the SOC, and spend the rest of the day apologizing.
Doing it safely is entirely a function of understanding the lockout policy. This post covers the math I use, and how I built it directly into ADValidate.
The three numbers that matter
Every domain’s lockout behavior comes down to three settings in the account lockout policy:
- Lockout threshold — how many bad passwords before an account locks (e.g.
5) - Observation window (
LockoutObservationWindow) — how longbadPwdCounttakes to reset after a failed attempt (e.g.30 minutes) - Lockout duration — how long the account stays locked once tripped
You can read these before spraying:
# With valid low-priv creds
crackmapexec smb DC01 -u user -p 'Pass' --pass-pol
# Or over LDAP / rpc
rpcclient -U '' -N DC01 -c 'getdompwinfo'
The one that saves you is the observation window. badPwdCount is not a permanent counter — it resets after the observation window elapses since the last bad attempt.
The safe-spray rule
Given a threshold of T, never send more than T - 1 attempts to any single account within one observation window. In practice I use T - 2 as a safety margin, because:
badPwdCountreplication between DCs is not instant- A real user may be fat-fingering their own password at the same time
So for a threshold = 5, window = 30 min policy, the rule is: at most 2–3 attempts per account per 30 minutes. One spray of a single candidate password across the whole user list burns exactly one attempt per account — well within budget. Wait out the window before the next password.
attempts_per_window = threshold - 2
spray_interval = observation_window + small_buffer
Don’t spray the accounts you can’t afford to lock
Two more precautions I always take:
- Filter out already-near-lockout accounts. Enumerate
badPwdCountfirst and skip anyone already atthreshold - 1. - Skip privileged/service accounts on the first pass. A locked-out
svc-sqlaccount is an outage, not a finding.
Where ADValidate fits
Manual spraying with a stopwatch works, but it’s error-prone across large user lists and multiple DCs. ADValidate reads the lockout threshold and observation window up front, then paces validation automatically so it never crosses threshold - N for any account — across LDAP, Kerberos, and SMB. Once a credential validates, it maps the access that credential actually grants, so the output is a prioritized list of usable footholds rather than a raw valid/invalid dump.
The guiding principle is simple: lockout policy is a hard constraint, not an afterthought. Treat it that way and spraying becomes a safe, repeatable part of the methodology instead of a gamble.
Mitigations (for the blue team reading this)
- Set a sane lockout threshold (not
0/ disabled) with a smart lockout / risk-based policy where possible - Alert on horizontal
badPwdCountgrowth across many accounts in a short window — that pattern is spraying - Enforce MFA on every externally reachable authentication surface
- Ban weak/seasonal passwords (
Summer2026!,Company123) with a password filter