git clone https://github.com/Intense-Visions/harness-engineering
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/claude-code/security-deserialization-attacks" ~/.claude/skills/intense-visions-harness-engineering-security-deserialization-attacks-d4dc5d && rm -rf "$T"
agents/skills/claude-code/security-deserialization-attacks/SKILL.mdDeserialization Attacks
Deserialization reconstructs objects from byte streams -- and in most languages, that reconstruction executes code, meaning an attacker who controls the serialized input controls what code runs on your server
When to Use
- Reviewing code that deserializes data from untrusted sources (user input, network, files)
- Evaluating the security of data interchange formats in an application architecture
- Assessing whether Java
, PythonObjectInputStream
, PHPpickle
, Rubyunserialize()
, or YAMLMarshal.load()
are used with external dataload() - Designing APIs or message formats and choosing between serialization approaches
- Investigating suspicious RCE (remote code execution) vectors in a web application
- Auditing session management that stores serialized objects in cookies or URL parameters
Threat Context
Insecure deserialization consistently ranks among the most severe vulnerability classes because it directly enables remote code execution -- the attacker does not need to find a separate code injection point; the deserialization mechanism itself executes arbitrary code:
- Equifax breach (2017, CVE-2017-5638): An Apache Struts vulnerability allowed remote
code execution through a crafted
header that triggered OGNL expression evaluation during deserialization. The attacker gained shell access to servers containing 145.5 million Americans' personal data. The vulnerability had a patch available two months before the breach; Equifax failed to apply it.Content-Type - Apache Commons Collections (2015): Security researchers published a gadget chain in Apache Commons Collections that achieved arbitrary command execution through Java deserialization. Because Commons Collections was on the classpath of virtually every Java enterprise application (including WebLogic, JBoss, Jenkins, and WebSphere), this single disclosure made thousands of production systems immediately exploitable.
- Jenkins (CVE-2016-0792): A deserialization vulnerability in Jenkins allowed unauthenticated remote code execution. The exploit used a Groovy-based gadget chain to execute operating system commands. Jenkins instances exposed to the internet were compromised en masse for cryptocurrency mining and as pivot points into internal networks.
- PHP object injection: PHP's
reconstructs objects and invokes magic methods (unserialize()
,__wakeup()
,__destruct()
). Attackers chain these magic methods across classes in the application and its dependencies to achieve file writes, SQL injection, or remote code execution. WordPress plugins have been repeatedly vulnerable to this pattern.__toString()
Instructions
-
Understand why deserialization is inherently dangerous. Native serialization formats (Java
, PythonSerializable
, PHPpickle
, Rubyserialize
, .NETMarshal
) are not data formats -- they are code formats. Deserializing an object means: allocating memory for the object, setting its fields to attacker-controlled values, and executing lifecycle methods (constructors, finalizers, magic methods). The attacker does not need to inject new code -- they manipulate existing classes in the application's classpath to form a chain of method calls that achieves their objective. This is the gadget chain concept.BinaryFormatter -
Never deserialize untrusted input with native serialization. This is the primary rule and it has no exceptions:
- Java: Do not use
on data from users, APIs, message queues, cookies, or any source the attacker can influence. Replace with JSON (Jackson, Gson) or Protocol Buffers. IfObjectInputStream.readObject()
cannot be removed, useObjectInputStream
(Java 9+) to allowlist specific classes before deserialization begins.ObjectInputFilter - Python: Do not use
on untrusted data. The pickle protocol executes arbitrary Python bytecode during deserialization -- there is no safe way to use pickle with untrusted input. Use JSON (pickle.loads()
), MessagePack, or Protocol Buffers.json.loads() - PHP: Do not use
on user-controlled input. Useunserialize()
. Ifjson_decode()
is required, PHP 7+ supports anunserialize()
option that restricts which classes can be instantiated -- use it with an explicit allowlist, neverallowed_classes
.true - Ruby: Do not use
on untrusted data. Use JSON (Marshal.load()
) or MessagePack.JSON.parse() - YAML: In Python, use
, neveryaml.safe_load()
. In Ruby, useyaml.load()
, neverYAML.safe_load()
(Ruby 3.1+ changed the default, but explicitYAML.load()
is still preferred for clarity). The unsafe YAML loaders instantiate arbitrary classes from YAML tags (safe_load
).!!python/object/apply:os.system - .NET: Do not use
,BinaryFormatter
,SoapFormatter
, orNetDataContractSerializer
. Microsoft has deprecatedObjectStateFormatter
as of .NET 8 due to its inherent insecurity. UseBinaryFormatter
or Protocol Buffers.System.Text.Json
- Java: Do not use
-
Use data-only interchange formats. JSON, Protocol Buffers, MessagePack, FlatBuffers, and similar formats serialize data (strings, numbers, arrays, maps) without encoding type information or executable behavior. Deserializing JSON cannot instantiate objects or execute methods -- it produces primitive data structures that the application then validates and maps to domain objects explicitly. This separation between data parsing and object construction is the fundamental defense against deserialization attacks.
-
When native serialization cannot be removed, apply defense in depth:
- Allowlist classes before deserialization. Java's
(JEP 290) lets you specify exactly which classes may be deserialized. Reject everything not on the list. This must happen beforeObjectInputFilter
is called -- once deserialization begins, gadget chain code has already executed.readObject() - Verify integrity before deserialization. Sign serialized payloads with HMAC. Before deserializing, verify the signature. If the attacker cannot forge the signature, they cannot inject a malicious payload. This requires the signing key to remain secret.
- Isolate deserialization in a sandbox. Run deserialization in a restricted process with minimal permissions, seccomp filters, or a container with no network access. If the gadget chain achieves code execution, the sandbox limits the damage.
- Monitor and alert on deserialization failures. Deserialization exceptions (unexpected class, invalid stream) often indicate an exploitation attempt. Log these failures with full context and alert on anomalous patterns.
- Allowlist classes before deserialization. Java's
-
Audit the codebase for deserialization points. Search for all uses of native deserialization APIs and categorize each by trust level of the input:
(Java),ObjectInputStream
/pickle.loads
(Python),pickle.load
(PHP),unserialize
(Ruby),Marshal.load
(Python/Ruby),yaml.load
(.NET),BinaryFormatter.Deserialize
(Java),readObject
(Java)XMLDecoder- For each occurrence: is the input from a trusted source (internal-only, signed)? If not, replace with a safe alternative or add allowlisting and integrity verification.
Details
-
How gadget chains work -- the Java example in detail: The attacker's goal is to execute
. ButRuntime.getRuntime().exec("malicious command")
is notRuntime
, so it cannot appear directly in the stream. Instead, the attacker finds a chain of serializable classes where: Class A'sSerializable
calls a method on a field. That field is set to an instance of Class B, whose method calls another method on another field. This chain continues until a class in the chain invokesreadObject()
or equivalent through reflection,Runtime.exec()
, orProcessBuilder
. The Apache Commons Collections gadget chain usesScriptEngine
(which calls any method via reflection) wired throughInvokerTransformer
and triggered byChainedTransformer
duringLazyMap
. The attacker crafts a serialized object graph that arranges these classes in the right configuration. Tools like ysoserial automate gadget chain construction for known library combinations.readObject() -
Python pickle as a universal code execution primitive: The pickle protocol includes an opcode (
) that calls any callable with any arguments. A malicious pickle payload can containREDUCE
or equivalent. Unlike Java gadget chains, which require finding suitable classes on the classpath, pickle exploitation is trivial because the protocol itself supports arbitrary function calls. There is no safe subset of pickle.os.system("rm -rf /")
is equivalent topickle.loads(untrusted_data)
.eval(untrusted_data) -
Deserialization in session management and view state: Some web frameworks store session state by serializing objects, encoding them (often Base64), and placing them in cookies or hidden form fields. If the serialized data is not signed or encrypted, the attacker modifies the cookie to inject a malicious serialized object. ASP.NET ViewState, Java JSF ViewState, and PHP session serialization have all been exploited this way. The mitigation is to sign and encrypt serialized session data (ASP.NET's
or a modern equivalent) and to verify the signature before deserialization.machineKey -
YAML deserialization as a code execution vector: YAML supports language-specific type tags that instruct the parser to instantiate objects. In Python,
executes the!!python/object/apply:os.system ["id"]
command. In Ruby,id
has been used to achieve code execution. The!!ruby/object:Gem::Installer
variants restrict the parser to basic data types (strings, numbers, lists, maps) and reject type tags. Any YAML parser configured to support arbitrary type tags is a deserialization vulnerability.safe_load
Anti-Patterns
-
Deserializing user input with native serialization formats. Accepting serialized Java objects in an HTTP request body, a message queue, or a cookie. The application may validate the deserialized object after construction, but by then the gadget chain has already executed. Deserialization attacks happen during deserialization, not after. Use JSON or Protocol Buffers for all external data interchange.
-
Allowlisting after deserialization. Checking the class of the deserialized object after
returns. This is too late. The gadget chain executes insidereadObject()
before control returns to the caller. Allowlisting must happen before deserialization, usingreadObject()
in Java or equivalent mechanisms that reject disallowed classes before theirObjectInputFilter
methods execute.readObject() -
Using
instead ofyaml.load()
. In Python's PyYAML library,yaml.safe_load()
without ayaml.load()
argument (or withLoader
prior to PyYAML 6.0) can instantiate arbitrary Python objects from YAML tags. A YAML configuration file that an attacker can modify becomes a remote code execution vector.Loader=yaml.FullLoader
restricts parsing to basic data types and is safe for untrusted input.yaml.safe_load() -
Trusting serialized objects in cookies or URL parameters. Storing a serialized user object in a cookie without signing it, then deserializing it on each request. The attacker modifies the cookie to contain a gadget chain payload. Even Base64 encoding provides no protection -- it is encoding, not encryption or signing. If serialized data must travel through the client, sign it with HMAC and verify the signature before deserialization.
-
Filtering known gadget chains instead of fixing the root cause. Maintaining a blocklist of known dangerous classes (e.g., blocking
in Java deserialization filters). New gadget chains are discovered regularly as new libraries are added to the classpath. A blocklist is always incomplete. Use an allowlist of the specific classes the application legitimately deserializes, or replace native serialization entirely.InvokerTransformer -
Assuming JSON is always safe. While JSON itself is a data-only format, JSON parsing libraries that support polymorphic type handling can reintroduce deserialization vulnerabilities. Jackson's
with@JsonTypeInfo
allows the JSON payload to specify which Java class to instantiate, enabling gadget chain attacks through JSON. UseJsonTypeInfo.Id.CLASS
with an explicit type allowlist, or avoid polymorphic deserialization entirely.JsonTypeInfo.Id.NAME