Deserialization Gadget Chains in Java
Deserialization gadget chains abuse existing application classes and callbacks to turn attacker-controlled serialized data into unintended behavior. In Java, the primary defense is to avoid deserializing untrusted data and, when unavoidable, constrain the permitted classes and object graph.
A gadget is an existing class or method whose normal behavior can become useful to an attacker when objects are reconstructed from serialized data. A gadget chain links several such behaviors so that deserialization reaches a sensitive operation without requiring the attacker to introduce new executable code.
Java native serialization is particularly important because `ObjectInputStream` can invoke special reconstruction mechanisms such as `readObject`, `readResolve`, and related callbacks. The security problem is therefore not simply that serialized fields can be modified; object construction itself can execute application or library logic.
A gadget chain is commonly described as a sequence of callbacks and method invocations ending in an impactful sink. The classes may be individually legitimate and harmless in their intended context, while their composition creates an unintended execution path. This is why auditing only the application's own serialization classes can miss the real attack surface.
The strongest mitigation is to replace native Java deserialization of untrusted input with a data format that does not reconstruct arbitrary object graphs. If native serialization is unavoidable, use `ObjectInputFilter` to enforce a narrow allowlist and limits on depth, references, array sizes, and total bytes, while also keeping dependencies patched and minimizing classes that implement serialization.
A filter is a defense-in-depth measure rather than proof that a serialized format is safe. An allowlist must be designed around the application's actual object model, and operational testing should verify that legitimate messages still work while unexpected classes and oversized graphs are rejected.
Key points
- Gadgets are existing classes whose normal callbacks can participate in an unintended execution path.
- A gadget chain is a composition of behaviors, not necessarily a single malicious class.
- Java native deserialization can invoke application and library callbacks during object reconstruction.
- Do not deserialize attacker-controlled Java serialization streams when an alternative data format is practical.
- Use narrowly scoped `ObjectInputFilter` policies as defense in depth when native serialization cannot be removed.
- Patch and minimize dependencies because third-party libraries can expand the available gadget surface.
Interview questions
What is a deserialization gadget chain?
It is a sequence of existing classes and callbacks that can be triggered while attacker-controlled serialized data is reconstructed. Each component may have legitimate behavior by itself. The security issue arises when those behaviors compose into an unintended path to a sensitive operation. The attacker is exploiting the application's reachable behavior rather than necessarily supplying executable code.
Why is Java native deserialization risky with untrusted input?
Native serialization reconstructs object graphs rather than simply returning primitive data. During that process, serialization-specific callbacks and class behavior can run. Consequently, an attacker who controls the serialized stream may influence which classes and object relationships are reconstructed. This creates an attack surface that is broader than ordinary data parsing.
How would you defend a Java service that must deserialize legacy Java objects?
First, I would isolate the deserialization boundary and determine exactly which classes the protocol requires. I would apply a restrictive `ObjectInputFilter` allowlist and impose resource limits such as maximum depth, references, array sizes, and stream bytes. I would also minimize and patch dependencies because reachable libraries influence the available gadget surface. Finally, I would plan migration to a non-native data format so the filter is defense in depth rather than the primary security boundary.
Why is a denylist of known gadget classes an incomplete defense?
A denylist assumes that the dangerous behavior can be comprehensively enumerated in advance. Gadget chains can involve application-specific classes, newly introduced dependencies, or combinations that were not previously recognized. Blocking individual known classes therefore does not establish that the remaining reachable graph is safe. A narrowly scoped allowlist is generally easier to reason about when the legitimate object model is small enough to enumerate.
What would you look for during a code review for deserialization vulnerabilities?
I would first locate every deserialization boundary, especially uses of `ObjectInputStream` and APIs that accept serialized Java objects indirectly. I would trace whether the input can originate outside a trusted boundary and identify the classes permitted during reconstruction. Then I would inspect filters, graph/resource limits, dependency versions, and serialization callbacks such as `readObject`. I would also look for opportunities to replace native serialization with a constrained data-transfer format.