Review of Java Deserialization
- Serialization is the process of transforming the state of an object into a stream of bytes. This stream of bytes can be saved to a file, transmitted over a network, or stored in some other way.
- Deserialization, on the other hand, is the process of converting the stream of bytes back into an object, recreating its original state in memory. This is useful when you want to retrieve an object from storage or when you receive an object over a network.
- In Java, the java.io.Serializable interface is a built-in mechanism for object serialization and deserialization. You mark a class as Serializable to indicate that its objects can be serialized. Then, you can use ObjectOutputStream.writeObject() to serialize an object and ObjectInputStream.readObject() to deserialize it. This makes it easy to save and load objects or transfer them across networks.
- There are also alternative ways to serialize and deserialize objects in Java. For instance, you can use libraries like Jackson for JSON serialization, Gson for JSON, or SnakeYAML for YAML. Each of these methods has its own advantages and may be more suitable for different use cases, but the built-in java.io.Serializable interface is the most common way for basic object serialization in Java.
Insecure Deserialization
-
The problem occurs if there is untrusted input in the deserialization process that can be controlled by a malicious threat actor. They are able to craft a serialized object that can lead to unintended consequences when deserialized.
- This is because of several reasons:
- The deserialization process does not perform input validation or check the content of the data it’s deserializing.
- Classes that implement the Serializable interface are designed to be serializable. While intended, it makes classes that implement Serializable potentially vulnerable to manipulation.
- Any class that the classloader can access during runtime can be used to exploit this vulnerability.
- There are magic methods such as readObject that are automatically invoked during the deserialization process. This can be used to construct complex gadget chains.
- Historically difficult to discover new chains:
- via static taint analysis (Gadget Inspector) due to runtime polymorphism. Polymorphism means that the actual method being executed can’t be determined at compile-time, making it challenging to predict and analyze these chains through static analysis tools.
- via dynamic fuzzing (SerHybrid) due to inefficiency as any method in the classpath can be used for gadget chains. Fuzzing each method to discover potential gadget chains can be inefficient and time-consuming.
What are Gadgets and Gadget Chains
- Gadgets are code (methods, classes etc) that can be used to further the exploitation of a deserialization vulnerability.
- These chains are a sequence of classes or methods that, when triggered through the deserialization process, can be exploited to achieve a malicious outcome. Here’s an explanation:
- Initial Gadget (Source): This is the starting point in a chain of classes or methods. It’s typically a method, often a custom readObject method in a serializable class.
- The deserialization process automatically invokes this method, making it a prime candidate for introducing malicious behavior.
- Last Gadget (Sink): The last gadget in the chain is the one that performs a malicious action.
- For example, it might execute a system command using Runtime.exec. The goal is to reach this point in the chain to execute a desired, typically harmful, action.
List of Useful Code Fragment for Deserialization
- Here is a list from a 2023 research of interesting sources and sinks that can be used to exploit Java Deserialization.
javax.management.BadAttributeValueException.toString()
- Creating a serialized BadAttributeValueException object can allow you to call the toString method of any class within the classloader.
java.util.Hashmap.hash → hashCode
- Calling the hash method will call the hashcode method on any arbitrary object.
com.sun.org.apache.xpath.internal.objects.XString.equals → toString()
- Calling the equals method as part of a larger gadget chain can allow the calling of any toString method of any object.
