WebVerse Pro | Challenge - Walkthrough

EasyWebVerse Pro5 min read

WebVerse Pro · Challenge

Walkthrough

Category: Web
Difficulty: Easy
XP: 50

Challenge Link


Sypnosis

A missing server-side MFA enforcement check allowed full authentication bypass — sessions were treated as fully authenticated immediately after password verification, letting an attacker skip the 6-digit OTP step entirely and access sensitive client data via /dashboard and /clients.


Skills Required

  • Basic understanding of HTTP session/cookie mechanics
  • Familiarity with authentication vs. authorization concepts
  • Web application manual testing (navigating/inspecting a live app)
  • Reading and reasoning about session tokens (base64/JWT-style decoding)

Skill Learned

  • Recognizing when MFA is enforced only at the UI/redirect level vs. as true server-side session state
  • Using inconsistencies in navigation/UI rendering as a signal of underlying auth state bugs
  • Decoding and interpreting session cookies to infer what the server considers "authenticated"
  • Distinguishing a genuine vulnerability from a red-herring attack surface (e.g., brute-forcing a 6-digit OTP vs. the actual access-control flaw)

Briefing


Exploitation

I did a quick scan and manual exploration in the pages but nothing really interesting, so I head straight testing the Registration and Sign In feature. Registration works as intended to be, I started by registering a fresh test account so I'd have credentials I fully controlled and wouldn't be messing with anyone else's data.

I logged in with those credentials, and as expected, I wasn't dropped straight into the app. I got redirected to /verify_otp, which asked me for a 6-digit code from an authenticator app.

My first instinct was to just try guessing the code — see how the endpoint reacted to a wrong value, maybe look for a way to speed up guessing. That went nowhere fast: Cloudflare's bot protection kicked in almost immediately and started challenging my requests. At that point I figured brute-forcing a 6-digit code with rate-limiting/WAF in front of it wasn't a realistic path, so I stepped back and looked at the page itself more carefully instead of hammering the endpoint.

That's when I noticed something odd. I was still sitting on the /verify_otp page — I hadn't entered a correct code, hadn't gotten past MFA at all — but the navigation bar at the top of the page wasn't showing a "Sign in" link like I'd expect for a user who isn't authenticated yet. It was showing "Dashboard" and "Sign out." That felt like a strong hint that the server already considered my session fully logged in, MFA or not.

So I tested that theory directly — I just clicked the "Dashboard" link right from the MFA page, without ever submitting a code. And it worked. I landed straight on the dashboard, fully authenticated as tester1, having never entered a single digit of the OTP.

From there I poked around the authenticated area and found a /clients page. Opening it up, I found client records with engagement notes attached — and sitting right there in one of the notes was the flag.


Root Cause Analysis

Once I had the flag, I wanted to understand why this worked, so I went back and decoded the session cookie I'd been issued at login. It turned out to be a signed (not encrypted) session object containing only:

json
{"user_id": 6}

That told me everything I needed to know: this cookie gets issued as soon as my password is verified — before MFA ever comes into play. Because it already encodes a valid user_id, any route that only checks "does a valid signed session cookie exist?" is going to treat my request as fully authenticated, whether or not I've actually completed the second factor.

In other words, the MFA step is only being enforced as client-side/routing-level UX — redirect me to /verify_otp right after login if I haven't done MFA yet — rather than as a server-side authorization check that every protected endpoint independently enforces. Once I had a valid session cookie, nothing downstream was re-checking whether MFA had actually been completed.

This lines up with a detail from the challenge briefing itself — "Cyrus added 'the 2FA thing' himself... he was very proud of the verification page" — which suggests this was a bespoke, one-off implementation rather than a vetted auth/session library. That's exactly the kind of environment where this class of gap tends to show up.


Impact

An attacker who obtains valid credentials for any account — via phishing, credential stuffing, a leaked password database, or similar — can fully bypass MFA and access the account (and, in this case, sensitive client financial/engagement data) without ever possessing the victim's second factor. This defeats the entire security purpose of implementing MFA in the first place, and directly exposes confidential client engagement notes.


Attack Chain Summary

StepAction
1Registered a test account (tester1)
2Logged in with valid credentials, got redirected to /verify_otp
3Tried brute-forcing the OTP — Cloudflare shut that down quickly
4Noticed the nav bar already showed "Dashboard"/"Sign out" while still on the MFA page
5Clicked "Dashboard" directly — landed on the authenticated dashboard, MFA never enforced
6Browsed to /clients and found the flag in the engagement notes

Remediation

  1. Track MFA completion as explicit session state, not as a side effect of navigation. Add an mfa_verified: false flag to the session at password-check time, and only set it to true after a correct OTP is submitted and validated.
  2. Enforce MFA state server-side on every protected route (/dashboard, /clients, and any other authenticated endpoint) via shared authorization middleware — never rely on the frontend/nav bar or redirect-on-login logic alone to gate access.
  3. Treat pre-MFA sessions as a distinct, limited-privilege state. Issue a short-lived "pending MFA" token/cookie at the password step, separate from the full session, which is only exchanged for a full session cookie after OTP success. This makes it structurally impossible for a pending-MFA session to satisfy a fully-authenticated route's checks.
  4. Add automated regression tests that assert protected routes reject requests from sessions with mfa_verified: false.