Insecure deserialization: why untrusted data should not become objects

Turning bytes back into objects is risky when the bytes come from an attacker. Learn why and what to use instead.

Bytes turning into objects with a warning mark

Serialization turns an object in memory into bytes or text for storage or sending. Deserialization turns it back into an object. It is normal and useful. It becomes dangerous when the data comes from an untrusted source.

Why it is dangerous

Some serialization formats can describe which classes to create and which code to run while loading. If an attacker controls the data, they may choose objects whose creation triggers harmful actions. Depending on the language and the libraries on your server, this can lead to:

  • Remote code execution.
  • Privilege escalation by changing fields (for example isAdmin=true inside a serialized session).
  • Denial of service with huge or deeply nested data.
  • Injection of unexpected objects (object injection).

Where it appears

  • Native formats of languages: Python pickle, Java ObjectInputStream, PHP unserialize(), Ruby Marshal, .NET BinaryFormatter (now discouraged).
  • Cookies, hidden form fields or API parameters holding serialized objects.
  • Message queues, caches and session stores that deserialize data an attacker might write to.
  • YAML loaders that can create arbitrary objects (use the "safe" loader).
  • Frameworks that deserialize request bodies into rich object types automatically.

How to prevent it

1. Do not deserialize untrusted data with native object formats. The best fix is to avoid it.

2. Use simple data formats. Prefer JSON (data only, no code or classes), and parse it into plain data structures.

3. Validate against a schema. Check the types, ranges and allowed fields (JSON Schema, Zod, Pydantic and similar) before using the data. Reject unexpected fields.

4. Sign or encrypt data you must round-trip. If you store state on the client (a cookie), protect it with a signature (HMAC) or authenticated encryption and verify before reading. Better still, store the state on the server and give the client only a random ID.

5. Use allow-lists if you must use rich types. Some libraries let you restrict which classes can be created. Keep that list tiny.

6. Limit resources. Set maximum size, depth and time for parsing.

7. Keep dependencies updated. Many known deserialization attacks rely on specific vulnerable libraries ("gadgets") on the classpath.

8. Run with least privilege and monitor for unusual deserialization errors.

A simple rule

Data should stay data. If a format can carry instructions about which code to run, do not feed it anything an outsider could have touched.

Takeaway: avoid native object deserialization for untrusted input, use JSON with strict schema validation, and sign anything you hand to the client.


Web Security series, Part 2 (more bug classes and hardening): 4 of 6. Previous: Secure file upload: what to check and where to store files.

Keep reading