Insecure deserialization
Improper handling of serialized data containing user input
Select your ecosystem
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
Excellent! 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:
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!
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! But how?
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.
But the truth is far from it. Whether a payload is a seemingly cryptic Base64 encoded string or a straightforward JSON object, if a malicious actor can influence its contents, the integrity of the application is at risk. Rather than relying on obscurity, it's paramount to ensure that any data - serialized or not - coming from untrusted sources is meticulously validated and sanitized. When a system is vulnerable to state manipulation from client-side data, it's a clear sign that there's a pressing need for a more secure design approach.
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.
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?
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:
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.
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
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.
Test your knowledge!
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
- Security implications of Pickle module
- A blog post about insecure deserialization attack in Python application
- Another post about insecure deserialization in Python
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