PHP's unserialize is not safe for untrusted input. The PHP manual says not to pass untrusted user input to it regardless of the allowed_classes option, because unserialization can load and run code through object instantiation and autoloading. Accept data as JSON with json_decode, and sign any serialized value you must store.
Why is unserialize dangerous?
unserialize rebuilds PHP objects from a string. Rebuilding an object can trigger methods that run automatically, such as a destructor or a wakeup hook, and PHP autoloading may load a class the attacker named. If any class available in your application does something dangerous in those methods, an attacker who controls the string can chain them. The weakness class is CWE-502, deserialization of untrusted data, and MITRE notes "PHP Object Injection" as another name researchers use for unsafe unserialize.
You do not need to write a vulnerable class yourself. Libraries in your vendor directory count. That is why dependency review belongs in this conversation.
A constructed example: a shopping site stores the cart as a serialized array in a cookie and calls unserialize on it at each request. A visitor edits the cookie to name an object type from a logging library instead. If that library has a method that writes to a file path when the object is destroyed, the visitor now controls a file write.
Does allowed_classes fix it?
It helps but does not make untrusted input safe. The manual describes allowed_classes as an array of accepted class names, false to accept no objects, or true. A class that is not allowed is created as __PHP_Incomplete_Class instead. The manual also states the option does not use inheritance, so allowing a parent class does not allow subclasses.
For data with no objects, unserialize($s, ['allowed_classes' => false]) removes the object injection path. But the manual's warning stands: do not pass untrusted input to it regardless of the option. The max_depth option limits nesting to avoid stack overflows, with a default of 4096. Treat these as seatbelts, not as permission.
What should you use instead?
The manual's advice is to use a standard interchange format such as JSON, with json_encode and json_decode. OWASP's Deserialization Cheat Sheet says the same for PHP: review every use of unserialize and how external input reaches it, and use JSON if serialized data must go to users. JSON parsing creates arrays, strings and numbers. It does not instantiate your classes.
After decoding, map the data to a typed object yourself. Validate each field, then construct the object with its constructor. That is also CWE-502's mitigation advice: populate a new object from the data so it passes input validation, instead of deserializing directly.
What if you must keep serialized data?
Sometimes legacy sessions, queue payloads or caches use PHP serialization. The manual suggests hash_hmac for validating data you store externally, so that it is "not modified by anyone but you." Compute the HMAC over the serialized string with a secret key held on the server, store it alongside, and verify with hash_equals before you call unserialize. OWASP agrees: sign messages when you serialize and refuse to deserialize anything without a valid signature. Then apply allowed_classes with a short explicit list as well.
Signing protects against tampering by outsiders. It does not help if the signing key leaks or if the server itself serializes attacker-chosen content.
How do you find the risk in an existing codebase?
Search for unserialize( and trace each argument back to its source. Cookies, request parameters, uploaded files, database fields filled from user input, cache entries and queue messages all count. Also look for functions that deserialize indirectly, such as file functions on user-supplied paths that can reach a stream wrapper. MITRE lists CVE-2012-3527 and CVE-2024-9314 among examples of unserialize issues in PHP applications.
What to do
- Grep for
unserializeand classify each call by where the input comes from. - Replace any call on user-influenced data with
json_decodeplus validation. - For legacy serialized data, add an HMAC check before deserializing and set
allowed_classes. - Keep the vendor tree small and updated, since libraries supply the classes attackers chain.
- Add a static analysis rule that flags new
unserializecalls for review. Safeguard's free scan at gold.safeguard.sh/scan can help review dependencies, up to 100 in the browser.
Quick check
Is unserialize with allowed_classes false safe for user input?
The manual still says not to pass untrusted input to unserialize regardless of that option. Use json_decode instead.
What does CWE-502 recommend?
Use signing such as an HMAC to confirm data is untampered, populate new objects from validated data, and limit allowed types, while noting an allow list alone is not enough.
Sources
Last reviewed: 18 October 2026.