visitor@hunterweygandt:~$ cat password-spray.html|

Password Spraying

Vulnerable Active Directory Lab · 2026

← Back to the other documents

Password Spraying

Summary

Password spraying inverts the usual brute-force model. Instead of trying many passwords against one account (which triggers lockouts), the attacker tries one common password against many accounts. A single weak, shared, or guessable password can unlock a large portion of a directory in one sweep, quietly, because each individual account sees only one failed attempt. It's one of the most common ways attackers gain an initial foothold in Active Directory.

MITRE ATT&CK: T1110.003 (Brute Force: Password Spraying) Target in this lab: the entire Employees OU (15 accounts sharing Ecorp2025)

The misconfiguration

There is nothing to plant for this piece, the vulnerability is baked into the environment: all 15 employee accounts share the password Ecorp2025 (company name + year). This shows a real-world failure mode, a default or onboarding password that users never change, or a weak complexity policy that everyone is satisfied with. The shared password is the vulnerability.

Execution

Create a username file on the attacking machine. First a username list built from the employees, then a spray of a single password across all of them over SMB.

cat > employees.txt << 'EOF'
jwellick
sharonk
oparker
ssepiol
gmercer
dflores
mwebb
panand
cbarnes
nreyes
tquinn
bortiz
dpratt
ecrane
runderhill
EOF

To produce the realistic failure-then-success pattern, I sprayed a wrong password first (generating the failure burst that defines the attack), then the correct one (the success):

# wrong password -> burst of 4625 failures across all accounts
netexec smb 10.0.0.88 -u employees.txt -p 'Summer2023' --continue-on-success

# correct password -> 4624 successes
netexec smb 10.0.0.88 -u employees.txt -p 'Ecorp2025' --continue-on-success
Kali terminal running netexec smb against the DC twice over the employees.txt list. The first spray with 'Summer2023' returns STATUS_LOGON_FAILURE for all fifteen accounts - the burst of failures that trips the detection. The second spray with 'Ecorp2025' returns a green [+] success for every one of the same fifteen accounts, confirming the entire employee roster shares that password.
Password spray, two passes: Summer2023 fails across all fifteen accounts (the 4625 burst the detection keys on), then Ecorp2025 succeeds on every one - the whole employee roster shares a single password. This is the initial foothold the rest of the chain is built on.

The correct-password spray validates against every account, one guessed password compromises the entire department. Unlike Kerberoasting and AS-REP roasting, there is no hash to crack: a successful spray pulls usable plaintext credentials immediately.

Telemetry generated

Each failed SMB logon writes Event 4625 (an account failed to log on) to the DC's Security log. The key fields:

Field Value Why it matters
win.system.eventID 4625 Failed logon
win.eventdata.targetUserName (varies per account) Different account each time
win.eventdata.ipAddress 10.0.0.76 Same source for every attempt
win.eventdata.logonType 3 Network logon (the SMB spray)
win.eventdata.subStatus 0xC000006A Bad password (account exists, wrong password)

The signature is not in any single event, it's in the aggregate: many 4625s, different win.eventdata.targetUserName values, all sharing one win.eventdata.ipAddress, in a tight window. Sub-status 0xC000006A (bad password on a valid account) rather than 0xC0000064 (user doesn't exist) confirms these are real accounts being sprayed, not username enumeration.

Audit requirement: "Audit Logon" enabled for Failure, which the lab's audit policy sets.

Detection

This is the first detection in the lab that is not a single-event match. No individual 4625 is suspicious, people fail logons constantly. The attack exists only in the _pattern_: a burst of failures across different accounts from one source. Detecting it requires a correlation rule that counts events over time, using Wazuh's frequency and timeframe attributes.

Custom rule (/var/ossec/etc/rules/local_rules.xml):

<group name="password_spray,attack,windows,">

  <!-- Password Spray: many failed logons from one source in a short window -->
  <rule id="100211" level="12" frequency="8" timeframe="60">
    <if_matched_sid>60122</if_matched_sid>
    <same_field>win.eventdata.ipAddress</same_field>
    <description>Possible password spray: 8+ failed logons from $(win.eventdata.ipAddress) within 60s</description>
    <mitre><id>T1110.003</id></mitre>
  </rule>

</group>

Rule design notes:

Result: rule 100211 fires at level 12. The alert's previous_output shows the exact chain of failed accounts (panand, jwellick, sharonk, oparker, ssepiol, gmercer, dflores, mwebb) all from 10.0.0.76 in the same window.

Wazuh Events filtered to raw event 4625 on the DC-VULN agent, showing 15 hits all within the same half-second. Each is a 'Logon Failure - Unknown user or bad password' (rule 60122) for a different targetUserName - runderhill, ecrane, dpratt, bortiz, and so on down the employee roster - but every single one comes from the same source ipAddress 10.0.0.76. One row is Wazuh's built-in 'Multiple Windows Logon Failures' correlation (rule 60204).
The spray signature in raw form: fifteen 4625 failures in one half-second, each a different account but all from the same source (10.0.0.76). No single failure is suspicious - the attack only appears when you notice one IP failing against the whole roster at once.
Wazuh Events filtered to rule.id 100211, showing a single hit: the custom correlation rule fires (level 12) with 'Possible password spray: 8+ failed logons from 10.0.0.76 within 60s', mapped to event 4625 and source 10.0.0.76. One alert summarizing the entire burst.
The custom rule 100211 collapses that whole burst into one alert: 8+ failed logons from 10.0.0.76 within 60s, tagged as a password spray with its MITRE technique. This is stateful correlation - the rule counts events over time and scopes them to a single source, rather than matching any one event's fields.

False positives / tuning: this rule keys on volume from a single IP, so it would also fire on a brute-force against ONE account (8 failures, same IP), not only a spray across many. For pure spray-specific detection, adding a different_field on targetUserName would require the failures to span distinct accounts, but in Wazuh that field-level condition is unreliable inside frequency rules, so this version relies on volume + source. In practice the overlap is acceptable: both spray and single-account brute-force from one source are worth alerting on. Legitimate high-failure sources (a misconfigured service account, a VPN concentrator NATing many users) would need to be tuned out with source-IP allowlisting in a production environment.

A stronger production version would also correlate a subsequent 4624 (success) from the same IP to flag when a spray actually lands, escalating from "spray attempt" to "spray success."

Remediation

Notes/Troubleshooting

← Back to the other documents