July 6, 2026
How I Bypassed Mandatory 2FA Three Ways on a multi tenant SaaS Platform (~$1,125)
By Gautam Sharma
3 min read
Redacted writeup. Program and target withheld per disclosure policy; endpoints and values below are illustrative.
The platform enforced mandatory 2FA on every account, yet exposed at least three paths to an authenticated session that never verified the second factor. I reported all three separately roughly $375 each and all three trace back to the same underlying flaw.
Background
Multi-tenant SaaS with a mandatory 2FA policy. The intended login is password → "2FA required" → OTP → session → dashboard, and the entire model depends on one invariant: the dashboard is unreachable without a valid OTP.
Most of the login flow is shared across tenants, so a defect in the shared components affects all of them. Scope differs per finding, and I note it in each section. All testing was performed against my own tenant.
1. Session issued before second-factor verification
On password submission, the /login response set a session cookie before the OTP step — the "2FA required" state existed only as a field in the JSON body, not as a restriction on the session. I skipped the OTP step and requested a protected endpoint directly with that session:
POST /login {"username":"me@example.com","password":"…"}
→ 200 OK
Set-Cookie: session=… {"status":"2fa_required"}
GET /api/account
Cookie: session=...
→ 200 OK
{ ...my account data... }POST /login {"username":"me@example.com","password":"…"}
→ 200 OK
Set-Cookie: session=… {"status":"2fa_required"}
GET /api/account
Cookie: session=...
→ 200 OK
{ ...my account data... }The endpoint returned authenticated data. To rule out a false positive, I confirmed the session could perform a state-changing action rather than only render a page, and reproduced it on a freshly registered account to exclude a persisted "remembered" session. The session carried no indicator of MFA completion, so nothing downstream enforced its absence.
The session was trusted at password validation and never reconciled against the OTP step 2FA gated the interface, not the session.
2. Password reset returns an authenticated session
Password reset is a separate path to a valid session, so I tested whether it re-enforced 2FA. It did not:
POST /password-reset/confirm
{"token":"<reset-token>","new_password":"..."}
→ 200 OK
Set-Cookie: session=... (authenticated, no OTP)POST /password-reset/confirm
{"token":"<reset-token>","new_password":"..."}
→ 200 OK
Set-Cookie: session=... (authenticated, no OTP)Precondition is the reset token victim's inbox, or a token-delivery flaw. That narrows it, but a mail compromise is exactly the case 2FA is supposed to cover, and here it doesn't: the reset issues a session without ever invoking the second factor.
3. OAuth login bypasses the 2FA policy
Authenticating through the linked provider returned a fully authenticated session without invoking the account's 2FA policy:
GET /auth/oauth/<provider> → provider flow
GET /auth/oauth/callback?code=...
→ Set-Cookie: session=... (authenticated, no OTP)GET /auth/oauth/<provider> → provider flow
GET /auth/oauth/callback?code=...
→ Set-Cookie: session=... (authenticated, no OTP)Precondition: the account is reachable through the provider — already linked, or linkable by email match. The email-match case is the more severe: an attacker-controlled provider identity carrying the victim's email authenticates as the victim with neither password nor OTP. The linking endpoint is worth testing in its own right — if it is unauthenticated or CSRF-able, this extends to pre-account-takeover. Scope: platform-wide, as OAuth runs through the shared flow.
Root cause
The same defect in all three: 2FA verification was bound to the password-login handler rather than to the authorization layer that gates every authenticated request. Each session-minting path — reset, OAuth — built its own session and never consulted the check, so enforcement existed only where it had been added by hand. Reset and OAuth run through the shared flow and are therefore platform-wide; the first finding was confined to the main tenant.
The fix is to treat MFA completion as a property of the session — a claim the protected routes require, so its absence is a hard deny — and to route every login path (password, reset, OAuth, invites, device-remember, mobile) through that single check, so a new path cannot silently omit it. Reset should require step-up; OAuth should not link accounts on an unverified email.
Impact
2FA on this platform functioned as an interface step on one of several authentication paths rather than an enforced control. A leaked password, a compromised inbox, or a linked provider each yield account access. The reset and OAuth bypasses affect every tenant via the shared flow; the first is limited to the main tenant. For scope: each bypass yields access to the authenticating account only — this is not a cross-tenant isolation break.
Let's connect
If this was useful and you want to talk bug bounty, methodology, or real-world testing, connect with me on.