Insecure Deserialization
debt(d5/e3/b5/t7)
Closest to 'specialist tool catches it' (d5). The term's detection_hints.tools lists semgrep, psalm, and phpstan — these are specialist SAST tools that can flag unserialize() calls with external input patterns. Standard linters won't catch this by default, but dedicated security scanners will.
Closest to 'simple parameterised fix' (e3). The quick_fix shows this is a straightforward replacement: swap unserialize($data) with json_decode($data, true) or add ['allowed_classes' => false]. However, if serialized objects are used throughout the codebase (cookies, sessions, caches), you may need to migrate data formats across multiple locations, pushing slightly beyond a one-liner.
Closest to 'persistent productivity tax' (b5). Once unserialize() is used for user-facing data (cookies, URL params), it becomes a persistent security concern that affects all code touching that data path. Every feature touching deserialized user data must consider gadget chains. The common_mistakes show this pattern often spreads to multiple storage layers (Redis, database), creating ongoing review burden.
Closest to 'serious trap' (t7). The misconception explicitly states developers wrongly believe signing/encoding prevents the attack — but HMAC verification after deserialization is too late because object construction (and magic methods like __wakeup, __destruct) execute during unserialize(). This contradicts how signature verification works in other contexts (verify-then-use), making it a serious cognitive trap where the 'obvious' secure pattern is actually vulnerable.
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]);