PHP Object Injection
debt(d5/e3/b3/t7)
Closest to 'specialist tool catches it' (d5), semgrep/psalm/phpstan rules flag unserialize() of tainted input, but standard runtime gives no warning and the vulnerability is silent until exploited.
Closest to 'simple parameterised fix' (e3), quick_fix is replacing unserialize() with json_decode() or adding allowed_classes: [] — pattern replacement but may require updating callers and data format migration.
Closest to 'localised tax' (b3), unserialize call sites are typically scoped to session/cookie/cache handling components rather than spread across the whole architecture, though legacy apps using serialized sessions have wider reach.
Closest to 'serious trap' (t7), the misconception that gadget chains are rare contradicts reality — magic methods (__wakeup, __destruct) fire implicitly during unserialize, which competent devs don't expect from a 'parsing' function, and encoding/encryption mitigations feel safe but aren't.
Also Known As
TL;DR
Explanation
PHP Object Injection is the practical exploit of insecure deserialization. When unserialize() processes attacker-controlled input, it instantiates arbitrary classes already loaded in the application's autoloader and calls __wakeup(), __destruct(), and __toString() as part of reconstruction. Attackers craft Property Oriented Programming (POP) chains — sequences of magic method calls across multiple classes that together achieve a malicious effect. Tools like PHPGGC (PHP Generic Gadget Chains) automate POP chain generation for popular frameworks. The fix: never unserialize untrusted data.
How It's Exploited
Common Misconception
Why It Matters
Common Mistakes
- Passing user-controlled cookies, GET/POST parameters, or database values to unserialize().
- Believing that base64 encoding or encryption of serialized data prevents injection — if the key is compromised or the encoding bypassable, it doesn't help.
- Using serialize()/unserialize() for caching or session storage of user-controllable data.
- Not auditing installed packages for gadget chains — popular frameworks have known deserialization gadgets.
Code Examples
// Deserializing user-controlled data:
$prefs = unserialize(base64_decode($_COOKIE['user_prefs']));
// Attacker crafts a serialized object with __destruct that writes a webshell
// Never unserialize user input — use JSON instead:
// Bad:
// $obj = unserialize($_COOKIE['prefs']);
// Safe: JSON for data exchange:
$prefs = json_decode($_COOKIE['prefs'] ?? '{}', true);
if (!is_array($prefs)) $prefs = [];
// If unserialize is absolutely needed, use allowed_classes:
$obj = unserialize($data, ['allowed_classes' => [SafeClass::class]]);
// Only SafeClass can be instantiated — gadget chains blocked
// Verify data integrity with HMAC before deserializing:
$expected = hash_hmac('sha256', $data, SECRET_KEY);
if (!hash_equals($expected, $signature)) {
throw new SecurityException('Data tampered');
}
$obj = unserialize($data, ['allowed_classes' => false]);