WEBVERSE

Loading...

easySQL InjectionPro

TicketSeason

A verified hockey-ticket marketplace that keeps you signed in between visits by stashing your account in a "remember me" cookie. The box office trusts whatever it finds in there.

The Scenario

TicketSeason resells North Atlantic Hockey League seats and prides itself on a snappy box office. To keep returning fans moving fast, the team serialized the signed-in member object straight into the "Keep me signed in" cookie and rebuilt the session from it on every request — no extra trip to the accounts database. Sign-up always provisions a plain member; the staff console is supposed to be the one door fans can't open. The shortcut they took to stay fast is the door.

Challenge Intel

Synopsis

The "remember me" cookie is base64-encoded PHP serialized state that includes the account role, and the app rebuilds the session from it without re-checking the database. Flip the role to staff — fixing the serialized string's length prefix — and the box-office console renders an internal settlement reference that's the flag.

What It Is

On sign-in with "Keep me signed in" ticked, TicketSeason sets a ts_remember cookie equal to base64_encode(serialize($member)), where $member is a small Member object with id, email, name, and role (default "member"). On every request the app does unserialize(base64_decode($_COOKIE['ts_remember'])) and restores the session straight from the cookie — it trusts the cookie's role field and never re-reads the role from the accounts database. The box office at /staff is gated on $member->role === 'staff'. A normal member hits a 403 whose copy states the area is "restricted to staff accounts" — that 403 is the only place the magic value "staff" is revealed. Solve: register through the real /register form (always provisioned as a member), sign in with remember-me, then base64-decode the ts_remember cookie. The plaintext is a PHP serialized Member; it contains s:4:"role";s:6:"member";. Change s:6:"member" to s:5:"staff" — the length prefix MUST drop from 6 to 5, because "member" is 6 characters and "staff" is 5; a wrong length makes unserialize() reject the whole string and the tamper silently fails. Re-base64-encode, replace the cookie, and reload. The staff console now renders the "nightly settlement reference," which is read from /flag.txt via file_get_contents and is reachable no other way. The Member class defines no magic methods (no __wakeup, __destruct, __toString), and the app vendors no autoloadable libraries, so injecting some other serialized object stays inert — this is strictly a property/ role tamper, not a gadget chain. /register and the profile-update endpoint both hardcode the role server-side and never read it from the request body.

Who It's For

Players new to insecure deserialization who want the cleanest possible version of the bug: no gadgets, no RCE — just reading a serialized blob, understanding that PHP string fields carry an explicit byte-length prefix, and editing both the value and its length to escalate from member to staff.

Skills You'll Practice

  • Recognising a base64-wrapped PHP serialized object in a cookie
  • Reading the PHP serialize() format (O:/s:/i: tokens and length prefixes)
  • Editing a serialized string value AND its length prefix so unserialize() accepts it
  • Spotting trust-the-client session state restored from a cookie instead of the database
  • Using an error page (403) as a source of the required magic value

What You'll Gain

  • A concrete feel for why serialized data is attacker-controlled input, not state
  • The length-prefix lesson: changing 'member' (6) to 'staff' (5) needs s:6 → s:5
  • Why a server must derive privilege from its own records, never from a cookie

Ready to hack TicketSeason?

Upgrade to Pro to unlock this challenge and the full library.