Insecure deserialization exploitation in Java
In our Blog, we have discussed the foundations of insecure deserialization. Now in this Blog, we’ll take a glance at multiple insecure deserialization exploitation in Java. So, let’s begin with the fundamentals.
What is the purpose of serialization?
Java uses a lot of serialization for various purposes:
- HTTP requests: Serialization is widely employed in the management of parameters, View State, cookies, etc.
- RMI (Remote Method Invocation): The Java RMI protocol, which relies entirely on serialization, is a cornerstone for remote communication in Java applications.
- RMI over HTTP: This method is commonly used by Java-based thick client web applications, utilizing serialization for all object communications.
- JMX (Java Management Extensions): JMX utilizes serialization for transmitting objects over the network.
- Custom Protocols: In Java, the standard practice involves the transmission of raw Java objects, which will be demonstrated in upcoming exploit examples.
Insecure Deserialization in Java
The use of Java de-serialization is to create objects from input sources. These input sources are byte streams in a range of different formats. Systems use de-serialization to ensure proper operation or to communicate with authorized external sources across networks. However, malicious or insecure byte streams can take advantage of de-serialization vulnerabilities.
Serialize Interface in Java
Java’s serialization technique transforms “objects” into byte streams so they can be saved for later usage in places like files, databases, and network traffic. We use the java.io implementation to make a Java object serializable. The writeObject() function of the ObjectOutputStream class is used to serialize an object. Only those objects from classes that implement java.io can be serialized.
Example:
Serialize interface in Java:
Example 2:
Deserialization interface in Java
De-serialization of a byte stream does not include calling the Object() function. In addition to this, the data is simply written to the fields of an empty object using reflection. The term “de-serialization” refers to the process by which a serialized object is transformed into its original state as an object instance. The stream of bytes from the other end of the network or a database can be read directly as the input to the de-serialization process.
To be exact, this stream of bytes, or serialized data, contains all the details about the instance that was serialized through the serialization process. This data consists of the class metadata and the values and type information for the instance field types. The same data is required when an object is rebuilt to create a new object instance. The JVM reads class metadata, which indicates whether an object’s class implements the “Serializable” or “Externalizable” interface, from the stream of bytes while de-serializing an object.
As you can see in this very basic example, the “vulnerability” here appears because the readObject function is calling other vulnerable functions.
Fingerprinting of insecure deserialization exploitation in Java
White Box
To identify potential serialization vulnerabilities in the codebase search for:
- Classes that implement the Serializable interface.
- Usage of java.io.ObjectInputStream, readObject, readUnshare functions.
Pay extra attention to:
- XMLDecoder utilized parameters defined by external users.
- XStream’s from XML method, especially if the XStream version is less than or equal to 1.46, as it is susceptible to serialization issues.
- ObjectInputStream coupled with the readObject method.
- Implementation of methods such as readObject, readObjectNodData, readResolve, or readExternal.
- ObjectInputStream.readUnshared.
- General use of Serializable.
Black box
For black box testing, look for specific signatures or “Magic Bytes” that denote Java serialized objects (originating from ObjectInputStream):
- Hexadecimal pattern: AC ED 00 05.
- Base64 pattern: rO0.
- HTTP response headers with Content-type set to application/x-java-serialized-object.
- Hexadecimal pattern indicating prior compression: 1F 8B 08 00.
- Base64 pattern indicating prior compression: H4sIA.
Now let’s start understanding insecure deserialization exploitation in Java with a practical demo.
First Login to the given account and observe that the session cookie contains a serialized Java object. Send a request containing your session cookie to Burp Repeater.
The thing to be noted here is this rO0 in our session cookie. There is a serialized java object because it starts with this rO0. As you can see I have already mentioned this is a specific signature.
Download the “ysoserial” tool and execute the following command. This generates a Base64-encoded serialized object containing your payload:
java -jar ysoserial-all.jar CommonsCollections4 'rm /home/carlos/morale.txt' | base64 -w 0
In Burp Repeater, replace your session cookie with the malicious one you just created. Select the entire cookie and then URL-encode it.
Forwad or send the request to solve the lab.
Prevention
Transient objects
A class that implements Serializable
can implement as transient
any object inside the class that shouldn’t be serializable. For example:
Avoid serialization of a class that need to implements Serializable
In scenarios where certain objects must implement the Serializable
interface due to class hierarchy, there’s a risk of unintentional deserialization. To prevent this, ensure these objects are non-deserializable by defining a final
readObject()
method that consistently throws an exception, as shown below:
Enhancing Deserialization Security in java
Customizing java.io.ObjectInputStream is a practical approach for securing deserialization processes. This method is suitable when:
- The deserialization code is under your control.
- The classes expected for deserialization are known.
Override the resolveClass() method to limit deserialization to allowed classes only. This prevents deserialization of any class except those explicitly permitted, such as in the following example that restricts deserialization to the Bicycle
class only:
Using a Java Agent for Security Enhancement offers a fallback solution when code modification isn’t possible. This method applies mainly for blacklisting harmful classes, using a JVM parameter:
-javaagent:name-of-agent.jar
It provides a way to secure deserialization dynamically, ideal for environments where immediate code changes are impractical.
Implementing Serialization Filters: Java 9 introduced serialization filters via the ObjectInputFilter interface, providing a powerful mechanism for specifying criteria that serialized objects must meet before being deserialized. These filters can be applied globally or per stream, offering a granular control over the deserialization process.
To utilize serialization filters, you can set a global filter that applies to all deserialization operations or configure it dynamically for specific streams. For example:
Leveraging External Libraries for Enhanced Security: Libraries such as NotSoSerial, jdeserialize, and Kryo offer advanced features for controlling and monitoring Java deserialization. These libraries can provide additional layers of security, such as whitelisting or blacklisting classes, analyzing serialized objects before deserialization, and implementing custom serialization strategies.
- NotSoSerial intercepts deserialization processes to prevent execution of untrusted code.
- jdeserialize allows for the analysis of serialized Java objects without deserializing them, helping identify potentially malicious content.
- Kryo is an alternative serialization framework that emphasizes speed and efficiency, offering configurable serialization strategies that can enhance security.
Recent Comments