flawopen.com/Insecure Deserialization/PHP unserialize()
No, and it cannot be made safe by filtering the string. unserialize() reconstructs objects, which means it instantiates classes the attacker names and triggers their magic methods. Use json_decode() for untrusted data — it produces only arrays and plain values, never objects with behaviour.
<?php // Any of these is exploitable $data = unserialize($_COOKIE['prefs']); $data = unserialize($_POST['state']); $data = unserialize(base64_decode($in)); // The attacker controls which class is // instantiated and all its properties: // O:8:"Logger":1:{s:4:"file"; // s:9:"/tmp/x.php";} // On destruction/wakeup, that class's // magic method runs with the attacker's // property values.
<?php // Use a data-only format $data = json_decode($input, true); if (!is_array($data)) { abort(400); } // If you must keep unserialize, at // minimum forbid objects entirely // (PHP 7+): $data = unserialize($input, [ 'allowed_classes' => false, ]); // Better: don't send state to the client // at all. Keep it server-side and hand // out an opaque session key. // If you must, sign it: hash_equals($mac, hash_hmac('sha256', $payload, $key));
The common objection is "but I have no class that runs shell commands, so this is fine". That misunderstands the attack. A property-oriented programming chain assembles a payload out of the classes your application already has — including those from your framework and every Composer dependency.
The attacker picks a class with a useful magic method — __destruct(), __wakeup(), __toString() — and sets its properties so that when that method runs, it calls into a second class, which calls a third, until the chain reaches something dangerous such as a file write or a command execution. Public tooling maintains ready-made chains for popular frameworks, so "no obvious gadget in my own code" offers no protection.
Attempts to sanitise serialized input — blocking the O: object marker, regex-matching class names, length checks — fail because the format is flexible and the parser is more permissive than the filter. Nested structures, references, and encoding variations all provide ways around string-level checks. The only reliable controls are not deserializing untrusted data at all, or passing allowed_classes => false so no object can be constructed.
It prevents object instantiation, which removes the magic-method route and therefore the POP chain. It is a genuine mitigation. You are still parsing attacker-controlled structured data, so validate the resulting shape — and if you are only handling arrays and scalars, JSON is the better tool.
An HMAC verified before deserializing prevents tampering, which closes the attack provided the key stays secret and you verify before calling unserialize. It is a valid pattern for things like signed cookies, but it makes key management a security dependency.
Structurally yes — all three reconstruct language objects from untrusted bytes and all three are exploitable via gadget chains. The magic-method names differ; the conclusion does not.