Some serialization formats don't just store data — they store object graphs that, when read back, run constructors, setters, and class methods. Hand attacker-controlled bytes to one of those deserializers and you've handed over arbitrary code execution. The category includes Java native serialization, Python pickle, .NET BinaryFormatter, and any "magic-method" format whose deserialization triggers behavior.
readObject hooks, setters, __reduce__, and other lifecycle methods. Crafted bytes can chain those into arbitrary calls.ysoserial generate them automatically.The original. ObjectInputStream.readObject() on attacker bytes has been remote code execution since the early 2000s. Famous CVEs in Apache Commons Collections, Spring, JBoss — all gadget-chain attacks via Java's built-in serialization.
Fix: stop using Java native serialization for anything that crosses a trust boundary. Use JSON or Protobuf instead. If you absolutely can't, use ObjectInputFilter (Java 9+) to allow-list classes. Better still — don't.
picklePickle is RCE by design — its __reduce__ protocol exists to let objects describe how to reconstruct themselves, including by calling arbitrary functions.
Rule: never pickle.loads anything you didn't produce yourself in a context you control. Use JSON for API payloads, Protobuf for service-to-service, and trust nothing pickled by clients.
BinaryFormatter and FriendsBinaryFormatter is officially deprecated and dangerous. So is NetDataContractSerializer, SoapFormatter, and unrestricted JavaScriptSerializer with type info. Use System.Text.Json or Newtonsoft.Json with TypeNameHandling.None. Microsoft's own guidance is clear: don't use BinaryFormatter for anything other than internal trusted data — and ideally not at all.
PyYAML's yaml.load — the default before recent versions — instantiated arbitrary Python objects from tags like !!python/object/apply:os.system. Use yaml.safe_load always. Same lesson in Ruby's YAML.load (use safe_load) and the older Java SnakeYAML defaults.
JSON is data — but libraries that embed type names so they can reconstruct subclasses (Jackson with default typing, Newtonsoft with TypeNameHandling=All, Gson with RuntimeTypeAdapterFactory) re-introduce the problem. Attacker tells the deserializer to instantiate any class on the classpath; gadget chains are back.
Fix: never enable open polymorphic typing on data from untrusted sources. If you must deserialize one of several types, use a tagged union with an explicit allow-list.
unserializeSame family. PHP magic methods (__wakeup, __destruct) trigger on deserialization. Use JSON. Avoid unserialize on anything untrusted.
JSON, Protobuf, Avro (with explicit schema), MessagePack, FlatBuffers, CBOR. These describe data — strings, numbers, arrays, maps. Deserializing them does not invoke arbitrary methods.
Java native serialization and pickle are fine within a trust boundary — caching your own data, intra-process state. They become RCE when they touch the network, a queue, a cookie, an upload, or the database where untrusted input lives.
Java's ObjectInputFilter rejects classes not on a list. Jackson's PolymorphicTypeValidator does the same. The list should be tiny — ideally only your own DTOs. Anything broader and gadget chains will eventually find a way through.
If a blob has to round-trip through an untrusted client (a session cookie, a queue), HMAC it with a server-side key. Reject anything whose signature doesn't verify. This stops attackers from fabricating new payloads — though it doesn't help if the secret leaks or the format itself is unsafe inside a trust boundary.
Gadget chains depend on classes already on your classpath. Every dependency you don't need is one fewer source of gadgets. Audit what's there with mvn dependency:tree, npm ls, pip list; remove what isn't pulling its weight.
Most disclosed deserialization CVEs come with new gadgets in popular libraries. Dependabot/Snyk alerts, automated minor-version updates, and a process for evaluating advisories are all part of the defense. Insecure deserialization is "low-frequency, high-severity" — when one lands in your stack, it matters.
Use JSON or Protobuf for anything that crosses a trust boundary. Reach for native object serialization only inside one process you fully control. If a feature seems to require deserializing complex objects from outside, redesign it — that's the bug, not the constraint.