Insecure Deserialization
Also Known As
TL;DR
Explanation
PHP's unserialize() reconstructs PHP objects from a byte string, calling __wakeup() and __destruct() magic methods on every instantiated object. If the codebase contains classes whose __destruct() or __wakeup() methods perform dangerous operations (file deletion, eval, system calls) — called gadget classes — an attacker can craft a serialized string that instantiates those classes with attacker-controlled properties, achieving remote code execution or file system manipulation without ever exploiting a separate vulnerability. This technique is called a gadget chain. Popular PHP frameworks (Laravel, Symfony, Yii) and libraries (Monolog, Guzzle) have had known gadget chains. Mitigations: never deserialize untrusted data with unserialize(); use JSON (json_decode) or a safe structured format instead; if deserialization is unavoidable, pass allowed_classes as the second argument to whitelist which classes may be instantiated. The PHPGGC tool generates ready-made gadget chain payloads for audit and testing.
Watch Out
Common Misconception
Why It Matters
Common Mistakes
- Storing serialized PHP objects in cookies or URL parameters — these are trivially modified by the client.
- Using unserialize() without the allowed_classes option — all loaded classes become potential gadgets.
- Verifying the HMAC signature after deserializing — the dangerous object construction happens during unserialize(), before your code can check the signature.
- Assuming private data (database, Redis) is safe — supply-chain attacks or SSRF can inject into internal stores.
Code Examples
// Cookie contains serialized object — attacker controlled:
$prefs = unserialize(base64_decode($_COOKIE['prefs']));
// If codebase has gadget classes, this is RCE
// Use JSON instead:
$prefs = json_decode(base64_decode($_COOKIE['prefs']), true);
if (!is_array($prefs)) {
$prefs = [];
}
// Or if you must deserialize, whitelist no classes:
$data = unserialize($input, ['allowed_classes' => false]);