File deserialization vulnerabilities often lead to arbitrary code execution. Learn how to secure your Node.js and PHP apps by avoiding native serialization.
During a recent security audit of a legacy Node.js application, I found a feature that allowed users to upload "state files" to restore their dashboard configuration. The code was using node-serialize to hydrate objects directly from the file system. It was a textbook case of how easily file deserialization can turn into a full system compromise.
If your application takes a blob of data—whether from a file, a database, or a request body—and turns it back into a complex object, you're playing with fire. If that data is tainted, an attacker can inject malicious objects that trigger unintended methods during the hydration process. This is exactly how you end up with arbitrary code execution, where an attacker runs their own logic on your server with your application's privileges.
The core issue is that native serialization formats (like PHP's serialize() or Node's v8.deserialize()) aren't just transporting data. They are transporting behavior.
When you deserialize an object, the runtime tries to reconstruct the class instance. If the language supports "magic methods"—like __wakeup() in PHP or custom toJSON() hooks in Node—the deserializer might automatically execute code defined within those objects.
I’ve seen developers try to "sanitize" serialized strings using regex, but that’s a losing battle. You’re essentially trying to perform deep packet inspection on a binary format that’s designed to be opaque. Instead, you need to rethink your secure data handling strategy entirely.
If you need to store complex state, stop using native serialization. I transitioned a high-traffic service from serialized PHP objects to standard JSON, and it saved us about 40ms in processing time per request while eliminating an entire class of vulnerabilities.
JSON is a data-interchange format, not an object-hydration format. It doesn’t execute logic. When you parse JSON, you get a plain object (or a Map), not an instance of a class with pre-defined methods.
Joi or Zod in Node.js, or JSON Schema in PHP. Never trust the structure of the incoming data.In Node.js, we often rely on serialize-to-js or similar libraries for convenience. Don't. If you're currently using a library that handles object graphs, swap it for JSON.parse() combined with a strict schema.
JAVASCRIPT// Don't do this: // const data = unserialize(fileContent); // Do this: const schema = z.object({ theme: z.string(), version: z.number(), }); try { const raw = JSON.parse(fileContent); const data = schema.parse(raw); // Throws if structure is unexpected const userConfig = new UserConfig(data.theme, data.version); } catch (e) { logger.error("Invalid data format"); }
PHP is particularly susceptible because of how unserialize() handles objects. If you're still using it, you need to move to json_decode() immediately. If you have to deal with legacy serialized data, at least use the allowed_classes option to restrict what can be instantiated.
PHP#6A9955">// Safer, but still not ideal $data = unserialize($fileContent, ["allowed_classes" => ["App\Models\UserConfig"]]);
However, the best path is to refactor your php security posture by ditching serialize() entirely. If you're dealing with sensitive data, make sure you're also following secret management best practices so that even if a deserialization vulnerability exists, the attacker can't easily pivot to your environment variables.
When we talk about node.js security, we often focus on dependencies, but the way we handle local state is just as critical. If you allow users to upload files that you later process, you have to treat those files as "untrusted input" until proven otherwise.
Q: Is it okay to use unserialize() if I sign the data with an HMAC?
A: Signing prevents tampering, but it doesn't solve the underlying problem of object injection if your secret key is ever compromised. It’s a "defense in depth" measure, not a primary fix.
Q: What if I need to store private properties? A: JSON doesn't support private properties. That's a feature, not a bug. If you need to store state, serialize the plain data to JSON and reconstruct the object state on the server side using your class constructor.
Q: Is Protobuf a better choice? A: Yes, Protobuf is excellent. It’s binary, fast, and requires a strict schema definition, which forces you to think about your data structure upfront. It's a great middle ground between performance and security.
I’ve learned the hard way that "convenience" is the primary driver of these vulnerabilities. Developers use native serialization because it's easy to save an entire object graph with one line of code. But that ease of use is exactly what an attacker exploits to achieve arbitrary code execution.
Next time, I’d recommend starting with a strict data contract before writing a single line of serialization code. It’s more work upfront, but it prevents the "fix-it-later" scramble when a security scan flags your codebase. If you’re interested in deeper dives on these topics, check out Insecure Deserialization: How to Secure Object Hydration in Node.js and PHP for more architectural patterns.
Master application security by implementing payload validation to prevent resource exhaustion and DoS attacks. Learn how to harden Node.js and PHP today.
Read moreStop arbitrary file write attacks by implementing strict validation and secure storage. Learn how to protect your Node.js and PHP apps from file overwrites.