Insecure deserialization

Serialization turns an in-memory object into a byte stream; deserialization rebuilds it. The vulnerability is that most engines run engine-defined callbacks during reconstruction (magic methods, reducers, readback hooks), so a deserializer handed attacker-controlled bytes does not just produce data, it executes behavior. Chaining those callbacks across classes already loaded in the application reaches command execution, file writes, or SSRF. Impact is typically remote code execution.

The shared model#

Every language variant follows the same three-part pattern:

  1. A sink that deserializes untrusted input: unserialize(), pickle.loads(), ObjectInputStream.readObject(), Marshal.load(), BinaryFormatter.Deserialize(), node-serialize.unserialize().
  2. A trigger: a callback the engine runs automatically on the rebuilt object (__wakeup/__destruct, __reduce__, readObject, init_with, an IIFE), or a type the deserializer is told to instantiate.
  3. A gadget chain: a sequence of methods on classes available in the target's codebase and dependencies that, once entered through the trigger, performs the attacker's action. The chain is assembled from whatever is on the classpath, which is why tools ship curated chains per framework.

Finding the sink#

  • Recognize serialized formats on the wire: PHP O:4:"User":..., Java base64 beginning rO0 (hex AC ED 00 05), Python pickle opcodes, .NET AAEAAAD/////, Ruby Marshal \x04\x08.
  • Look in cookies, hidden fields, Authorization/custom headers, caches, message queues, and import/upload features.
  • Confirm by tampering a byte and watching for a deserialization-specific error, then move to a controlled object.

Languages#

Tools#

  • ysoserial (Java), ysoserial.net, PHPGGC (PHP gadget chains), marshalsec (Java/JVM)

References#

  • PortSwigger Web Security Academy: Insecure deserialization
  • OWASP: Deserialization Cheat Sheet

Cookie Consent

We use cookies to enhance your experience. Learn more