Insecure Deserialization: Detection, Exploitation Paths, and Prevention Guide

Retool's new app builder is where AI-generated code ships safely
Building apps with AI is easy. Getting them to production safely is another story.
Deserialization is the process of converting a serialized data format (a byte stream, JSON string, or binary blob) back into an in-memory object. Most programming runtimes support serialization natively: Java's ObjectInputStream, Python's pickle module, PHP's unserialize() function, and .NET's BinaryFormatter all provide this capability. The vulnerability arises when these mechanisms accept data from untrusted sources without validating that the reconstructed object is what the application expects.
The severity of insecure deserialization is high because exploitation can reach remote code execution without any other vulnerability being present. The attack surface is often invisible: serialized data arrives in cookies, HTTP parameters, binary protocols, message queue payloads, and API responses. This guide covers how deserialization vulnerabilities work in the four highest-risk runtimes, how to find them in existing code, and the specific controls that eliminate the risk.
How deserialization vulnerabilities work by runtime
Each runtime has distinct serialization mechanisms, attack vectors, and remediation approaches, so a generic fix for one language does not apply to another. Java relies on ObjectInputStream and gadget chains targeting common libraries; Python exposes arbitrary code execution through the pickle and yaml.load() APIs; PHP triggers magic methods via unserialize(); and .NET's deprecated BinaryFormatter allows arbitrary type instantiation. Understanding the platform-specific mechanics is essential before auditing your codebase, because the grep patterns, exploit payloads, and remediation API calls differ significantly across each environment. Work through the runtime that matches your stack first, then verify coverage across any polyglot services.
Java: ObjectInputStream and gadget chains
Java's native serialization uses ObjectInputStream.readObject() to reconstruct objects from a byte stream. Every class that implements java.io.Serializable and defines a readObject() or readResolve() method can execute custom code during deserialization. Attackers exploit 'gadget chains': sequences of Serializable classes already on the application's classpath whose deserialization methods chain together to execute system commands. Tools like ysoserial generate pre-built payloads for Apache Commons Collections (CommonsCollections1-7), Spring Framework, Groovy, and 30+ other common libraries. The attack requires only that these libraries are on the classpath, not that the application directly uses their dangerous methods.
Python: pickle and yaml.load()
Python's pickle module serializes Python objects using a bytecode protocol that can embed arbitrary Python instructions, including calls to os.system() and subprocess.Popen(). When pickle.loads() processes attacker-controlled data, those embedded instructions execute during deserialization. There is no safe way to use pickle with untrusted data. Similarly, yaml.load() with the default Loader (or FullLoader) allows YAML documents to instantiate arbitrary Python objects using the !! tag syntax: !!python/object/apply:os.system ['id'] executes the id command. Fix both by using json instead of pickle and yaml.safe_load() instead of yaml.load().
PHP: unserialize() and POP chains
PHP's unserialize() reconstructs PHP objects from a serialized string. PHP Property-Oriented Programming (POP) chains exploit magic methods (__wakeup, __destruct, __toString) that are automatically called on deserialized objects. Attackers craft serialized strings that create objects whose magic methods chain together to execute arbitrary code. PHP object injection is detected by searching for unserialize() calls that accept HTTP parameters, cookies, or user-submitted data. Fix: replace unserialize() with json_decode() for data exchange; if unserialize() is required, validate with an HMAC before deserializing.
.NET: BinaryFormatter and TypeNameHandling
Microsoft has deprecated BinaryFormatter in .NET 5+ and removed it in .NET 9 because it cannot be made safe against untrusted input. BinaryFormatter, NetDataContractSerializer, and SoapFormatter all allow arbitrary type instantiation during deserialization, enabling gadget chain attacks. JSON.NET's TypeNameHandling.All or TypeNameHandling.Auto settings also allow type confusion attacks when processing untrusted JSON. Fix: migrate to System.Text.Json (which does not support arbitrary type loading) or DataContractSerializer with a DataContractResolver that allowlists permitted types.
Detecting insecure deserialization: code review and DAST
Detection requires both static analysis of the codebase and dynamic testing of running applications, because not every deserialization call is visible through code review alone. Static analysis identifies the API calls and data flow paths that introduce risk, while DAST confirms which of those paths are reachable and exploitable from an external attacker's position. The grep patterns below target the highest-signal API calls in each language; each hit should be followed by manual data-flow tracing to determine whether the input source is user-controlled. Pair static findings with Burp Suite's active scanner or the Java Deserialization Scanner extension to verify exploitability without requiring manual payload crafting.
Static analysis: grep patterns by language
Java: grep -r 'ObjectInputStream\|readObject()\|XMLDecoder' src/ and follow each instantiation to identify whether the input source is user-controlled. Python: grep -r 'pickle.loads\|yaml.load\|marshal.loads\|jsonpickle.decode' and verify whether the data source is external. PHP: grep -r 'unserialize(' and check whether the argument derives from $_GET, $_POST, $_COOKIE, or database fields that could be attacker-influenced. .NET: grep -r 'BinaryFormatter\|NetDataContractSerializer\|TypeNameHandling' and verify usage context. Semgrep rulesets exist for all four languages (search for deserialization in the Semgrep registry).
DAST: Java deserialization magic bytes
Java serialized data always begins with the bytes 0xAC 0xED 0x00 0x05, which appear as rO0AB when base64-encoded. Use Burp Suite Pro's Intruder or the Java Deserialization Scanner extension to automatically detect these bytes in HTTP requests and inject ysoserial payloads. Monitor application logs and out-of-band DNS callbacks (Burp Collaborator or interactsh) for DNS lookups or HTTP callbacks triggered by deserialization, which confirm exploitability without requiring visible output. Most OAST-capable scanners (Burp, Nuclei templates) have built-in deserialization detection.
Runtime detection: JVM deserialization filters
Java 9+ includes JEP 290 deserialization filters that log or block deserialization of non-allowlisted classes. Enable a global filter with the system property jdk.serialFilter=class1;class2;!* to allowlist only the classes your application legitimately deserializes. In Java 17+, context-specific filters can be set per-stream with ObjectInputFilter.Config.setSerialFilter(). Review filter violation logs to identify attempted exploitation even when the filter blocks execution. Serialization monitoring agents (SerialKiller, not-so-serial) provide similar protection for Java 8 environments that cannot easily upgrade.
Briefings like this, every morning before 9am.
Threat intel, active CVEs, and campaign alerts, distilled for practitioners. 50,000+ subscribers. No noise.
Prevention controls by risk tier
Remediation options differ substantially in how completely they eliminate the vulnerability versus how much they reduce exploitability at the margins. Tier 1 eliminates the vulnerability class outright by removing native deserialization of untrusted data entirely; lower tiers add detection, allowlisting, and blast-radius reduction when full elimination is not immediately feasible. Apply controls starting from the highest tier your architecture can support, and treat lower-tier controls as defense-in-depth layering rather than standalone solutions. For legacy Java applications that cannot immediately migrate away from ObjectInputStream, combining Tier 2 HMAC signing with Tier 3 JEP 290 allowlist filters and Tier 4 seccomp process isolation provides meaningful risk reduction while a longer-term migration is planned.
Tier 1: Eliminate native deserialization of untrusted data (eliminate the vulnerability)
Replace native serialization with data-only formats that cannot instantiate arbitrary types. JSON (Jackson with default typing disabled, Gson, System.Text.Json) and Protocol Buffers serialize data values without type information that could trigger code execution. This eliminates the deserialization vulnerability class entirely. Apply this to: session storage (use server-side sessions with a session ID cookie instead of serialized session objects in cookies), inter-service communication (use JSON over HTTP/gRPC instead of Java RMI), and configuration loading (use YAML safe_load or JSON instead of Java serialization).
Tier 2: Sign and verify serialized data before deserialization (detect tampering)
When native deserialization cannot be avoided: compute an HMAC-SHA256 of the serialized byte stream using a server-side secret key, store the HMAC alongside the serialized data, and verify the HMAC before calling readObject(). Reject deserialization if the HMAC does not verify. This prevents an attacker from modifying serialized data because they do not have the signing key. This control does not prevent exploitation if the serialized data was generated by an attacker in the first place (e.g., a crafted payload submitted as a new object), but it prevents tampering with data serialized by the server.
Tier 3: Deserialization allowlist filters (reduce exploit surface)
Implement a class allowlist that permits deserialization only of the specific classes the application legitimately expects. For Java: use JEP 290 filters. For PHP: use unserialize(['allowed_classes' => ['MyClass', 'OtherClass']]) with the allowed_classes option. For .NET: use DataContractSerializer with a known-types list. The allowlist approach reduces the available gadget chain surface to only allowlisted classes, making exploitation harder but not impossible if any allowlisted class participates in a gadget chain. Combine with Tier 1 or Tier 2 controls for defense in depth.
Tier 4: Runtime isolation (limit blast radius)
Run deserializing components in a minimally privileged process that cannot execute system commands, cannot write to sensitive filesystem locations, and cannot make outbound network connections. Use OS-level controls: Linux seccomp profiles that block execve() system calls, AppArmor/SELinux profiles that restrict filesystem access, or container security contexts with readOnlyRootFilesystem. If deserialization RCE is achieved, the attacker is confined to a limited sandbox rather than gaining full server access. This does not prevent deserialization attacks but substantially reduces their impact.
The bottom line
Insecure deserialization is a vulnerability with a straightforward root cause and a clear path to elimination: do not deserialize untrusted data using native serialization mechanisms. Every application that uses Java ObjectInputStream, Python pickle, PHP unserialize(), or .NET BinaryFormatter with externally-supplied data is at risk. The remediation priority is: replace native deserialization with data-only formats first, add HMAC signing if replacement is not immediately feasible, and apply runtime isolation as a defense-in-depth layer. Start with a grep-based audit of the patterns above, verify each with code review, and prioritize any deserialization endpoint that accepts HTTP parameters, cookies, or message queue payloads.
Frequently asked questions
What is insecure deserialization and why is it dangerous?
Insecure deserialization occurs when an application accepts serialized data from an untrusted source and deserializes it without validating the data's integrity, type, or content. During deserialization, the runtime calls methods on reconstructed objects. If an attacker can craft a malicious serialized payload that triggers dangerous method calls during reconstruction, the result is remote code execution, privilege escalation, or authentication bypass, without requiring any other vulnerability.
How does a Java deserialization attack work?
Java deserialization attacks exploit the ObjectInputStream.readObject() method, which reconstructs objects from a byte stream. Attackers craft payloads using gadget chains: sequences of classes already on the application's classpath (Apache Commons Collections, Spring, etc.) whose readObject() or readResolve() methods can be chained to execute arbitrary system commands. The tool ysoserial automates payload generation for known gadget chains. The payload is delivered wherever the application accepts serialized Java data: cookies, HTTP parameters, RMI endpoints, JMX, or custom binary protocols.
How do I detect insecure deserialization in my code?
In Java: search for ObjectInputStream instantiation and readObject() calls with user-controlled input sources. In Python: search for pickle.loads(), yaml.load() without Loader=yaml.SafeLoader, and marshal.loads() with untrusted data. In PHP: search for unserialize() calls on user-controlled data. In .NET: search for BinaryFormatter, NetDataContractSerializer, and JavaScriptSerializer with TypeNameHandling. SAST tools like Semgrep, SonarQube, and Checkmarx include deserialization detection rules. DAST scanners like Burp Suite Pro detect Java deserialization via magic byte detection in HTTP traffic.
What is a gadget chain in Java deserialization?
A gadget chain is a sequence of existing Java classes that, when their deserialization methods are invoked in the correct order, ultimately execute an attacker's payload (typically a system command). Gadget chains exploit legitimate classes already present on the application's classpath, which is why vulnerable applications do not need to contain any inherently malicious code. The gadget chain transforms the deserialization operation into a code execution path using entirely trusted classes in unintended combinations.
How do I prevent insecure deserialization in Java?
The safest approach is to not deserialize untrusted data using native Java serialization. Replace ObjectInputStream with a data-only format like JSON (Jackson, Gson) or Protocol Buffers. If native deserialization is unavoidable: use a deserialization filter (JEP 290, available in Java 9+, backported to Java 8u121+) to allowlist only the expected classes; use the SerialKiller or NotSoSerial Java agent to block known dangerous classes; sign serialized data with an HMAC so tampering is detected before deserialization; and isolate the deserializing component in a minimal-privilege JVM process that cannot execute system commands.
What is the difference between Python pickle and YAML deserialization vulnerabilities?
Python pickle.loads() executes arbitrary Python code embedded in the pickle stream. Any attacker who can control pickle input can achieve remote code execution. yaml.load() with the default Loader also executes Python objects embedded in YAML via the !! tag syntax. Both are equally dangerous. Fix: replace pickle with json for data exchange; replace yaml.load() with yaml.safe_load() which restricts to primitive types only. Never use pickle for data received from external sources regardless of transport security, because the risk is in the format itself, not the transport.
Can a WAF or runtime agent detect deserialization attacks?
Java deserialization payloads start with the bytes 0xACED0005 (hex) or rO0AB (base64). A WAF can detect and block these magic bytes in HTTP bodies and parameters. However, this detection is bypassable if the application accepts binary data in custom formats, if the WAF does not inspect all content types, or if attackers use alternative serialization triggers (RMI, JMX). Runtime application self-protection (RASP) agents that hook the JVM can block deserialization of dangerous classes regardless of delivery channel and are more robust than WAF-based detection alone.
Sources & references
Free resources
Critical CVE Reference Card 2025–2026
25 actively exploited vulnerabilities with CVSS scores, exploit status, and patch availability. Print it, pin it, share it with your SOC team.
Ransomware Incident Response Playbook
Step-by-step 24-hour IR checklist covering detection, containment, eradication, and recovery. Built for SOC teams, IR leads, and CISOs.
Get threat intel before your inbox does.
50,000+ security professionals read Decryption Digest for early warnings on zero-days, ransomware, and nation-state campaigns. Free, daily, no spam.
Unsubscribe anytime. We never sell your data.

Founder & Cybersecurity Evangelist, Decryption Digest
Cybersecurity professional with expertise in threat intelligence, vulnerability research, and enterprise security. Covers zero-days, ransomware, and nation-state operations for 50,000+ security professionals every morning.
Win a $2,495 Black Hat pass.
Full-access to Black Hat USA 2026 in Las Vegas. Subscribe free to enter.
