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

Discovering insecure deserialization vulnerabilities

Insecure deserialization in action

Try now

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.

Note: The following commands are being run on a Windows OS
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! Run:

echo rO0ABXNyACRjb20uZHVuZ2VvbnNhbmRtb25leS5zdGF0ZS5FcXVpcG1lbnQPjNIbPjmvPwIAAVsABWl0ZW1zdAACW0l4cHVyAAJbSU26YCZ26rKlAgAAeHAAAAAFAAAAAAAAAAAAAAABAAAAAAAAAAA= | base64 --decode
Demo terminal

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.

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.

Insecure deserialization mitigation

1. Avoid java custom serialization

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

2. 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.

3. Restrict the types of objects you allow to deserialize.

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:

4. 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.