Skip to content
← Back to findings
June 2026CriticalCVSS 9.1Target: Private client area

Second factor skipped: the session was issued before the code was checked

The first login step already returned the final session cookie. The code screen was a visual gate: skipping it opened the account.

  • CWE-304
  • CWE-287
  • MFA Bypass
  • Broken Authentication

Summary

Sign-in was split into two steps: username and password first, one-time code second. The code check ran on the server and correctly rejected any code that did not match.

The flaw was earlier: the first step response already carried the final session cookie. The second factor authorised nothing. It only decided which screen came next.

The 9.1 severity reflects what we verified: remote access to the full account with the password alone.

Technical details

The session was issued too early

The response to the username and password submission carried the session cookie, with no pending-verification marker:

First step response
POST /auth/login HTTP/2
{"user":"[TEST ACCOUNT]","pass":"[REDACTED]"}
 
HTTP/2 200 OK
Set-Cookie: session=[TOKEN]; Path=/; HttpOnly; Secure
 
{"step":"otp_required"}

The second step granted no authorisation

We stopped the flow there and went straight to the private area. The session was already valid:

Access without completing the second factor
GET /portal/dashboard HTTP/2
Cookie: session=[FIRST STEP TOKEN]
 
HTTP/2 200 OK
 
[+] Session accepted with no code ever submitted
[+] The "step" field only governed the client screen

Scope inside the account

That session worked against the personal data and account management endpoints, not just the landing view:

Scope check
GET  /api/profile          -> 200 OK   full personal data
GET  /api/documents        -> 200 OK   account documentation
POST /api/profile/email    -> 200 OK   email change accepted
 
[!] Changing the email would also hand the attacker the password reset flow

We ran everything against a test account created for the audit, including the email change. We accessed no client accounts, and we reported the flaw immediately through the channel agreed in the rules of engagement.

The case is published anonymised, with no names and no domains, after the fix and with the client’s prior authorisation.

Impact

  • Second factor rendered useless: the password alone granted access
  • Full account access: the attached documentation was in plain view
  • Persistent account takeover: changing the recovery email kept the attacker inside
  • Leaked credentials usable again: the second factor no longer protected accounts with compromised passwords
  • Unreliable audit trail: nothing on the server distinguished a verified sign-in from an unverified one

Remediation

  • Do not issue the session until authentication completes. The first step should return a temporary flow identifier, not the final cookie.
  • Carry the state in the token itself. If something is issued earlier, it must hold a pending-verification state the server checks on every request.
  • Check that state server side on every private endpoint, not in the presentation layer.
  • Invalidate the flow after repeated failures and give it a strict expiry.
  • Log flow start and verification separately, so the trail reflects what actually happened.

References

More write-ups

Does your two-step login issue the session before the code is checked?

Tell us which assets you want tested. We reply within 24 hours.

Request an auditCall