Insecure deserialization is what happens when an application rebuilds objects from serialized data — a byte stream, a base64 blob, a pickled Python object — without verifying that the data actually came from a trusted source or was never tampered with. Because deserialization in Java, .NET, PHP, and Python can trigger code execution as a side effect of simply reconstructing an object, an attacker who controls the serialized input can often turn "just loading some data" into full remote code execution (RCE) on the server. It sits inside OWASP's A08:2021 Software and Data Integrity Failures category, and for Indian dev and security teams shipping APIs, session tokens, and message queues fast, it is one of the most under-tested bug classes in production.
This guide covers how deserialization attacks actually work — object injection, gadget chains, and magic methods — what real-world impact looks like, how teams discover it before attackers do, and the concrete defences that close it off for good.
What Insecure Deserialization Actually Is
Serialization converts an in-memory object into a storable or transmittable format — a byte stream in Java, a string in PHP's serialize(), a byte stream via Python's pickle, or a binary blob via .NET's BinaryFormatter. Deserialization reverses that process, reading the format back into a live object in memory. The vulnerability isn't in serialization itself — it's in deserializing data the application does not control, because reconstructing an object is never just "reading data." It's executing code.
Where Untrusted Data Sneaks In
Untrusted serialized data typically enters through places developers don't expect to be attack surface:
- Session tokens or "remember me" cookies stored as serialized objects instead of opaque IDs
- API parameters, hidden form fields, or ViewState blobs that carry serialized state between requests
- Message queues and caching layers (Redis, RabbitMQ) where producers and consumers trust each other implicitly
- File uploads or import features that accept a serialized payload as a shortcut for "bulk data"
How Object Injection Becomes Remote Code Execution
The exploit path runs through what security researchers call a gadget chain. A "gadget" is any class already present in the application or its dependencies whose constructor, destructor, or a special "magic method" performs some action automatically the moment an object of that type is created or destroyed — no attacker code needs to be uploaded at all, because the vulnerable classes are already sitting in memory.
Each language exposes its own magic methods as the entry point:
- Java —
readObject()andreadResolve()are invoked automatically byObjectInputStreamduring deserialization. - PHP —
wakeup()anddestruct()fire automatically when an object is unserialized or garbage-collected. - Python —
pickle.loads()callsreduce()andsetstate(), and can execute arbitrary functions specified inside the pickled payload. - .NET —
BinaryFormatterand similar formatters triggerOnDeserializationcallbacks and constructor logic during type reconstruction.
Runtime.exec(), eval(), or os.system() that runs an attacker-supplied command. Public tools such as ysoserial (Java) and ysoserial.net (.NET) exist specifically to auto-generate these chains from common libraries already present on the classpath, which is exactly why the attack is so reliable against unpatched, widely-used frameworks.
Real Impact: Why This Bug Class Is Rated So High
The clearest large-scale demonstration was CVE-2017-9805 in the Apache Struts 2 REST plugin, where the framework used XStream to deserialize XML requests without any type filtering. Any unauthenticated request to a vulnerable endpoint could trigger full remote code execution, and the flaw was actively exploited in the wild within weeks of disclosure, landing it in CISA's Known Exploited Vulnerabilities catalogue. It is a textbook case of the pattern: a mainstream framework, a widely deployed plugin, and one missing allow-list turning into a mass-exploitable RCE.
Know your vulnerabilities before attackers do
Run a free VAPT scan — takes 5 minutes, no signup required.
Book Your Free ScanLanguage by Language: Where the Danger Lives
Every major stack has a native, "convenient" deserialization mechanism that was never designed with untrusted input in mind. Knowing which call sites to search for is the fastest way to scope an assessment.
| Language / Platform | Dangerous Native Call | Typical Attack Entry Point |
|---|---|---|
| Java | ObjectInputStream.readObject() | Session cookies, RMI, JMS message bodies |
| PHP | unserialize() | Cookies, cached objects, form fields |
| Python | pickle.loads(), yaml.load() (unsafe mode) | Cache values, inter-service messages, ML model files |
| .NET | BinaryFormatter, SoapFormatter, NetDataContractSerializer | ViewState, cached session objects, WCF endpoints |
| Ruby | Marshal.load() | Session cookies, cached data |
How Teams Discover Insecure Deserialization
Deserialization flaws rarely show up in a basic automated scan, because a scanner needs to recognise a serialized format, know the target language's gadget landscape, and actually trigger a callback to confirm the finding — not just flag a suspicious-looking string. A meaningful assessment combines:
- Code review for native deserialization calls — grepping for
readObject,unserialize,pickle.loads,BinaryFormatter, and equivalents, then tracing whether the input reaching them is attacker-controlled. - Dependency inventory — gadget chains rely on classes already present on the classpath or in installed packages. Knowing exactly which libraries (and versions) are loaded tells you whether a known public gadget chain applies.
- Dynamic testing with proof-of-concept payloads — tools like
ysoserialgenerate serialized payloads for known gadget chains in common Java libraries, used in a controlled test to confirm exploitability rather than just theorise it. - Fuzzing serialized inputs — mutating a legitimate serialized blob and observing crashes, errors, or unexpected object state changes, which often surfaces object-injection logic flaws even where full RCE isn't achievable.
rO0AB at the start is the signature of a serialized Java object; O:8:"ClassName" is PHP's unserialize() format. Either one showing up where you expected an opaque token is worth immediate investigation.Defence: How to Actually Stop It
The most reliable fix is architectural, not patch-level: stop deserializing untrusted input into native objects at all.
- Prefer data-only formats. JSON and well-validated XML (with external entities disabled) carry data, not executable object graphs — deserializing them into simple data-transfer objects, with strict schema validation, removes the magic-method attack surface entirely.
- Never deserialize untrusted input with a native, unrestricted deserializer. If a legacy system genuinely requires native serialization internally, keep it confined to trusted, internal-only channels — never expose it to anything reachable from outside your trust boundary.
- Enforce class/type allow-lists. Java's
ObjectInputFilter(since JEP 290), and equivalent allow-list mechanisms in other languages, restrict deserialization to a fixed, reviewed set of expected classes, so an attacker-supplied gadget class is rejected before its constructor ever runs. - Add integrity checks. Sign serialized data with an HMAC using a server-side secret, and verify the signature before deserializing anything. This doesn't stop a determined attacker who has already found another way in, but it blocks tampering with data that merely passed through the client.
- Run deserialization with least privilege. If native deserialization can't be avoided, isolate it in a low-privilege process or sandboxed environment so a successful gadget chain can't reach the rest of the system.
- Keep frameworks and libraries patched. Struts, Jackson, XStream, and similar libraries have shipped fixes for known gadget classes repeatedly — an outdated dependency is often the entire vulnerability.
Building This Into Your Security Programme
Insecure deserialization rarely shows up in a generic vulnerability scan the way an outdated TLS cipher or a missing security header does — it needs someone to actually trace which endpoints accept serialized input and whether the libraries behind them carry known gadget chains. That's exactly the kind of finding a structured, code-aware security assessment is built to catch, rather than a surface-level automated crawl.
At Bachao.AI, built by Dhisattva AI Pvt Ltd, automated VAPT assessments are designed to surface exactly these deeper, logic-level findings alongside the standard OWASP Top 10 coverage — including deserialization entry points, outdated serialization libraries, and missing integrity checks — instead of stopping at what a generic scanner flags. If your team hasn't specifically checked for native deserialization of untrusted input, a free VAPT scan is a fast way to find out where it's exposed. Teams handling regulated personal data can also review our DPDP compliance guide for how a code-execution vulnerability like this maps to breach-notification obligations, and our blog has more deep-dive guides like this one.
Frequently Asked Questions
What is insecure deserialization in simple terms?
What is a gadget chain?
Which languages are affected by insecure deserialization?
ObjectInputStream), PHP (unserialize()), Python (pickle), .NET (BinaryFormatter and related classes), and Ruby (Marshal) all expose native deserialization mechanisms that can trigger code execution. Any language whose deserializer can invoke constructors, destructors, or callback methods on attacker-influenced data carries the same risk pattern.How do you defend against insecure deserialization?
Is insecure deserialization still part of the OWASP Top 10?
How do security teams find deserialization vulnerabilities before attackers do?
ysoserial to confirm real exploitability rather than a theoretical finding.