Java Top

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Overview

In this tutorial, we'll explore how an attacker can use deserialization in Java code to exploit a system.

We'll start by looking at some different approaches an attacker might use to exploit a system. Then, we will look at the implications of a successful attack. Finally, we will look at some best practices to help avoid these types of attacks.

2. Deserialization Vulnerabilities

Java uses deserialization widely to create objects from input sources.

These input sources are byte-streams and come in a variety of formats (some standard forms include JSON and XML). Legitimate system functionality or communication with trusted sources across networks use deserialization. However, untrusted or malicious byte-streams can exploit vulnerable deserialization code.

Our previous article on Java Serialization covers how serialization and deserialization work in greater depth.

2.1. Attack Vector

Let's discuss how an attacker might use deserialization to exploit a system.

For a class to be serializable, it must conform to the Serializable interface. Classes that implement Serializable use the methods readObject and writeObject. These methods deserialize and serialize object instances of the class, respectively.

A typical implementation of this may look like this:

public class Thing implements Serializable {
    private static final long serialVersionUID = 0L;

    // Class fields

    private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
        ois.defaultReadObject();
        // Custom attribute setting
    }

    private void writeObject(ObjectOutputStream oos) throws IOException {
        oos.defaultWriteObject(); 
        // Custom attribute getting
    }
}

Classes become vulnerable when they have generic or loosely defined fields and use reflection to set attributes on these fields:

public class BadThing implements Serializable {
    private static final long serialVersionUID = 0L;

    Object looselyDefinedThing;
    String methodName;

    private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
        ois.defaultReadObject();
        try {
            Method method = looselyDefinedThing.getClass().getMethod(methodName);
            method.invoke(looselyDefinedThing);
        } catch (Exception e) {
            // handle error...
        }
    }

    // ...
}

Let's break down the above to see what is happening.

Firstly, our class BadThing has a field looselyDefinedThing which is of type Object. This is vague and allows for an attacker to make this field any type that is available on the classpath.

Next, what makes this class vulnerable is that the readObject method contains custom code that invokes a method on looselyDefinedThing. The method we want to invoke uses the field methodName (which can also be controlled by the attacker) via reflection.

The above code is equivalent to the following in execution if the class MyCustomAttackObject is on the system's classpath:

BadThing badThing = new BadThing();
badThing.looselyDefinedThing = new MyCustomAttackObject();
badThing.methodName = "methodThatTriggersAttack";

Method method = looselyDefinedThing.getClass().getMethod(methodName);
method.invoke(methodName);
public class MyCustomAttackObject implements Serializable {
    public static void methodThatTriggersAttack() {
        try {
            Runtime.getRuntime().exec("echo \"Oh, no! I've been hacked\"");
        } catch (IOException e) {
            // handle error...
        }
    }
}

By using the MyCustomAttackObject class, the attacker has been able to execute a command on the host machine.

This particular command is harmless. However, if this method were able to take custom commands, the possibilities of what an attacker can achieve are limitless.

The question that still stands is, “why would anyone have such a class on their classpath in the first place?”.

Classes that allow an attacker to execute malicious code exist widely throughout open source and third-party libraries that are used by many frameworks and software. They are not often as simple as the above example but involve using multiple classes and reflection to be able to execute commands of similar ilk.

Using multiple classes in this way is often referred to as a gadget chain. The open-source tool ysoserial maintains an active list of gadget chains that can be used in an attack.

2.2. Implications

Now that we know how an attacker might gain access to remote command execution let's discuss some of the implications of what an attacker may be able to achieve on our system.

Depending on the level of access that the user running the JVM has, the attacker may already have heightened privileges on the machine, which would allow them to access most files across the system and steal information.

Some deserialization exploits allow an attacker to execute custom Java code that could lead to denial of service attacks, stealing of user session or unauthorized access to resources.

As each deserialization vulnerability is different and each system set up is different, what an attacker can achieve varies widely. For this Reason, vulnerability databases consider deserialization vulnerabilities high risk.

3. Best Practices for Prevention

Now that we have covered how our system might be exploited, we'll touch on some best practices that can be followed to help prevent this type of attack and limit the scope of potential exploits.

Note, there is no silver bullet in exploit prevention, and this section is not an exhaustive list of all preventative measures:

  • We should keep open source libraries up to date. Prioritize updating to the latest version of libraries when available.
  • Actively check vulnerability databases such as the National Vulnerability Database, or CVE Mitre (to name a few) for newly declared vulnerabilities and make sure that we are not exposed
  • Verify the source of input byte-stream for deserialization (use secure connections and verify the user, etc.)
  • If the input has come from a user input field, make sure to validate these fields and authorize the user before deserializing
  • Follow the owasp cheatsheet for deserialization when creating custom deserialization code
  • Limit what the JVM can access on the host machine to reduce the scope of what an attacker can do if they are able to exploit our system

4. Conclusion

In this article, we've covered how an attacker may use deserialization to exploit a vulnerable system. In addition, we have covered some practices to maintain good security hygiene in a Java system.

As always, the source code is available over on GitHub.

Java bottom

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

>> CHECK OUT THE COURSE
Junit footer banner
Comments are closed on this article!