The fundamental problem with serialization is that the areas that can be attacked are too large and are constantly spreading. The object graph (an image of a group of objects connected by reference) is restored by the readObject method of ObjectInputStream. This method allows you to instantiate objects of almost any type on your classpath, as long as the type implements Serializable. In the process of deserializing the byte stream, this method can execute any type of code that is on the classpath and implements Serialize. Therefore, all these types are targeted for attack.
Even if you remove the vulnerability from a serializable class that can be attacked, the application itself may still be vulnerable. If you create a gadget chain by combining methods that can perform dangerous processing (called gadgets) from serializable type methods, sometimes it happens that arbitrary native code can be executed. In fact, in 2016, a hack using this mechanism was made against the San Francisco Metropolitan Transit Agency Municipal Railway.
As mentioned above, you can easily create a stream that takes a lot of time to deserialize without using a gadget, and you can make a DoS attack with this. Such streams are called deserialization bombs.
// Deserialization bomb - deserializing this stream takes forever
static byte[] bomb() {
Set<Object> root = new HashSet<>();
Set<Object> s1 = root;
Set<Object> s2 = new HashSet<>();
for (int i = 0; i < 100; i++) {
Set<Object> t1 = new HashSet<>();
Set<Object> t2 = new HashSet<>();
t1.add("foo"); // Make t1 unequal to t2
s1.add(t1); s1.add(t2);
s2.add(t1); s2.add(t2);
s1 = t1;
s2 = t2;
}
return serialize(root); // Method omitted for brevity
}
In the above code, there are 100 layers of structure in which one HashSet has two HashSet elements. When you create a HashSet instance, you have to calculate the hashcode for each element. Therefore, it is necessary to call hashCode 2 to the 100th power, and the processing is not completed at all.
How should we deal with the above attacks related to serialization?
** The best way is not to deserialize. ** ** ** There is no reason to use Java serialization in new systems. ** ** There are better mechanisms for translating byte sequences and objects, which are called cross-platform structured-data representations.
Typical cross-platform structured-data representations are JSON and Protocol. The most important difference between JSON and Protobuf is that JSON is text-based and human-readable, while Protobuf is binary and more efficient.
Serialization in Java may be unavoidable in the maintenance and development of legacy systems. In such cases, you should take measures such as ** do not deserialize untrusted data **. In particular, do not accept RMI traffic from untrusted sources.
If serialization in Java is unavoidable and the safety of the deserializing data is questionable, the object deserialization filter java.io.ObjectInputFilter (introduced from Java 9 and backported to 6,7,8) ) Should be used. This provides the ability to filter the stream before it is deserialized. You can decide whether to reject or accept on a class-by-class basis. At this time, you can select either whitelist format or blacklist format, but since blacklists can only protect against known threats, whitelists should be adopted. The filtering feature also prevents excessive memory usage and excessively deep object graphs, but it does not prevent the deserialization bombs mentioned above.
Recommended Posts