Sometimes a vulnerability is so simple it makes you question whether you found anything at all.
You sit there staring at it. Waiting for the other shoe to drop. Waiting for the part where it gets complicated.
It doesn’t get complicated.
The server asked: “Has the user completed 2FA?” I said: “Yes.” The server said: “Welcome.”
That was it.
The Flow
Redacted-secure.com — a financial services platform. They advertised 2FA prominently. “Bank-grade security.” The whole pitch.
The login flow:
- Submit username + password → server validates → returns
{"success": true, "require_2fa": true} - Enter TOTP code → server validates → returns
{"success": true, "2fa_complete": true}→ session created
Standard. Fine. In theory.
I submitted valid credentials. Got the require_2fa: true response. Normal.
Instead of submitting a TOTP code, I intercepted the response from step 1 and modified it:
Changed "require_2fa": true to "require_2fa": false.
The client — which trusted the server’s response to determine the next step — skipped the 2FA screen entirely. Proceeded to the authenticated session.
I was logged in. No TOTP. No 2FA. Full access.
Why This Works (And Why It Shouldn’t)
The application was making a classic mistake: using client-side logic to enforce a security requirement.
The server told the client “you need 2FA.” The client acted on that instruction. But the server never independently verified whether 2FA had actually been completed before issuing a session.
It trusted the client’s report of what the client had done. In a security flow. That protects financial accounts.
The fix is exactly what you’d expect: the server should never issue a session token until it has internally verified 2FA completion. The client’s state is irrelevant. The server tracks completion server-side and issues the session only after its own verification confirms success.
The Report
Title: 2FA Bypass via Client-Side Response Manipulation — Server Issues Session Without Verifying 2FA Completion
Severity: Critical
Impact: Complete bypass of two-factor authentication for any account. By modifying the server response after password validation to indicate 2FA is not required, the client skips the 2FA step and the server issues a full authenticated session. An attacker with stolen credentials can access any 2FA-protected account without the second factor.
Fix: Track 2FA completion server-side per login session. Issue session tokens only after server-side verification of TOTP code. Never use client-reported state to make session issuance decisions.
Response: 1 hour. They were not happy. Patch in 4 hours — server now maintains 2FA state internally and validates before token issuance.
Bounty: $$$$
The Lesson
Never trust the client.
This is lesson one of web security. It is also apparently a lesson that needs repeating constantly.
The client can modify anything: request parameters, response data, localStorage, cookies, headers. Anything that arrives at your server or is processed by your client-side code can be tampered with.
Security decisions — authentication, authorization, access control — must be enforced server-side. Not signaled by the client. Not confirmed by the client. Enforced. By the server. Every time.
A 2FA implementation that asks the client whether 2FA is complete is not a 2FA implementation. It’s theater.
Reported through the official bug bounty program. Testing was conducted on my own account only. Domain redacted per responsible disclosure norms.