Java Deserialization RCE: Deep Dive into Vulnerabilities & Defenses
Java’s serialization mechanism was introduced to enable easy persistence and transmission of Java objects. While powerful, this mechanism can be incredibly dangerous when misused — especially in the form of deserialization vulnerabilities, which can lead to Remote Code Execution (RCE). In this article, we’ll explore how deserialization vulnerabilities arise, look at real-world attack scenarios, understand gadget chains, and walk through practical defenses to secure your Java applications.
What Is Java Deserialization?
Serialization is the process of converting a Java object into a byte stream. Deserialization is the reverse — turning the byte stream back into an object. Java’s ObjectInputStream handles this operation seamlessly, but it comes with a risk: deserialization does not validate the origin or safety of the object being reconstituted.
If untrusted data is passed to deserialization routines, it can trigger the execution of malicious code — leading to full system compromise.
Real-World Example: The Commons Collections RCE
One of the most notorious deserialization vulnerabilities exploited in the wild involved the Apache Commons Collections library.
The Vulnerable Code
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream()); Object obj = ois.readObject(); // Dangerous: Accepts untrusted input
If a malicious object graph crafted with a gadget chain from Commons Collections is sent over the socket, readObject() can lead to remote code execution.
The Gadget Chain Concept
A gadget is a class with side-effects during deserialization. An attacker chains these together to cause malicious behavior.
Commons Collections 3.1 provided such gadgets:
- The attacker would use
InvokerTransformerandPriorityQueueto cause arbitrary method invocation. - When deserialized, a method like
Runtime.getRuntime().exec("calc.exe")could be triggered.
Impact: A payload like this could be generated using tools such as ysoserial, leading to command execution on the target machine.
Notable Real-World Exploits
- Jenkins CVE-2015-7501
Jenkins once allowed unauthenticated users to exploit a deserialization vulnerability via theXStreamlibrary, resulting in remote command execution. - WebLogic CVEs (e.g., CVE-2019-2725)
Oracle’s WebLogic Server suffered several deserialization flaws over the years where attackers could exploit T3 protocol to execute code remotely. - Apache Struts CVEs
Several versions of Struts were vulnerable due to insecure object handling in theStruts2framework.
Defending Against Deserialization Attacks
1. Never Deserialize Untrusted Input
This is the golden rule. If the origin of the serialized data isn’t trusted, don’t deserialize it.
Bad:
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("userInput.ser"));
Object obj = ois.readObject(); // Dangerous if input is untrusted
Good:
Avoid using serialization at all for untrusted communication. Use safer data formats like JSON, XML (with secure parsers), or Protobuf.
2. Use a Whitelist ObjectInputStream
Extend ObjectInputStream to explicitly allow only known classes.
class SafeObjectInputStream extends ObjectInputStream {
private static final Set<String> allowedClasses = Set.of("com.example.MySafeClass");
public SafeObjectInputStream(InputStream in) throws IOException {
super(in);
}
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
if (!allowedClasses.contains(desc.getName())) {
throw new InvalidClassException("Unauthorized deserialization attempt", desc.getName());
}
return super.resolveClass(desc);
}
}
3. Use Libraries That Harden Serialization
- SerialKiller: A drop-in replacement for
ObjectInputStreamwith advanced filtering and blacklists. - Apache Commons IO ValidatingObjectInputStream: Restricts allowed types during deserialization.
- XStream: Offers fine-grained control over allowed classes with
allowTypes().
4. Upgrade & Monitor Libraries
Many gadget chains exploit older versions of libraries. Keep dependencies up to date and watch CVE databases for newly discovered vulnerabilities.
# Use OWASP Dependency-Check dependency-check --project MyApp --scan ./target
5. Isolate Deserialization Logic
If deserialization is absolutely necessary (e.g., for legacy support), run it in a sandbox or separate service/container with reduced privileges.
6. Use Serialization Proxies Pattern
Rather than allowing an object with complex structure to be directly serialized, use a safe intermediate class to control deserialization behavior.
private static class SerializedForm implements Serializable {
private final String safeData;
SerializedForm(MyObject obj) {
this.safeData = obj.getSafeData();
}
private Object readResolve() {
return new MyObject(safeData);
}
}
Summary
| Vulnerability Cause | Example | Defense |
|---|---|---|
| Trusting user input for deserialization | ois.readObject() | Avoid untrusted input |
| Use of vulnerable gadget chains | Commons Collections | Use a class whitelist |
| Legacy or unsafe libraries | WebLogic, Struts | Patch and isolate |
Final Thoughts
Java deserialization RCEs are a critical security issue that still affects many enterprise applications. The threat is not just theoretical — multiple high-profile breaches have involved insecure deserialization paths.
The good news? With awareness, code hygiene, and the right libraries, you can avoid these pitfalls. Treat deserialization with the same suspicion you would give to SQL or shell inputs — it’s a potential attack surface that needs strict validation and minimal exposure.
Further Reading

