Skip to main content
  1. Posts/

Insecure Deserialization - Applied Review

·9 mins
web BSCP
Table of Contents

What is Serialization?
#

As the name suggests, serialization is the process of converting complex data into a simpler format that can be send as a stream of bytes. This makes it easier to write complex data into memory, files, or databases. It also simplifies the process of sending complex data over a network connection that varies in which components need to be sent.

The thing we are sending in a serialized format is referred to as an object and when we serialize them, their attributes and values are preserved.

Deserialization is the process of restoring the serialized stream of bytes into a replica of the original object. This object is then used however the application specifies it to be used.

I find it easy to imagine something like a video game character. Your character might have certain customizations like their appearance and their equipment. This data is serialized when it is just sitting in memory and ready to be pulled by the game engine, but gets deserialized whenever that character object needs to be rendered or modified in some way.

Of course, the way that you serialize data will depend on the programming language you use and which libraries you opt to use. You’ll also see serialization sometimes referred to as marshalling for Rust and pickling for Python.

What is Insecure Deserialization?
#

I initially found it hard to understand how serialization could present issues because it seems like it just performs a simple action. The reason these problems arise is that user-inputs can sometimes be deserialized in such a way that when serialized, that data causes the application to behave differently.

You might be able to input data that when deserialized, injects an object of an entirely different class that triggers some malicious logic when a certain error occurs.

This would enable an attack that would be completed before the deserialization is completed, which means that the website itself might not even have to interact with the malicious object for it to be vulnerable.

Exploitation
#

In order to effectively exploit these kinds of vulnerabilities, we need to have a good understanding of what kinds of technologies are in use and how to identify them.

PHP Serialization Format is mostly human-readable with letters that represent the data type and numbers to represent the length of a particular entry.

For example, imagine a User object like this:

$user->name = "gabe";
$user->isLoggedIn = true;

When serialized, it would look something like this:

O:4:"User":2:{s:4:"name":s:4:"gabe"; s:10:"isLoggedIn"🅱️1;}

This serialized data can be interpreted like this:

An object whose name is four characters long is called 'user' and it contains 2 attributes:
	{The first attribute is a string of four characters called 'name' and its value is a four character string called 'gabe'}
	{The second attribute is a string of ten characters called 'isLoggedIn' and contains a boolean value of '1' }

If you have source code access, you can probably find instances of serialize and unserialize being used and begin poking around there.

Java Serialization Format uses binary, which is more difficult to read, but can still be recognized by the bytes it typically begins with. In hexadecimal this would be aced and in Base64 it would be rO0. You will see this often implemented with java.io.Serializable.

Manipulating Serialized Objects
#

Sometimes the object that is created will be persistent and is based off of our input. For example, imagine a website that serializes some information about our session and stores it in a cookie. We might see that serialized object in an HTTP request like this:

O:4:"User":2:{s:8:"username";s:6:"gabe";s:7:"isAdmin";b:0;}

If this cookie is used for authorization to some kind of admin functionality, we might be able to just serialize our own object that contains the isAdmin boolean set to true.

This scenario is not super common though, and we will likely need to try more aspects of the object like the data types being used.

PHP-based logic is generally easier to manipulate because the == operator tries to help out when doing comparisons. Like if you compare the integer 5 and the string "5", it will still evaluate to true even if the string only starts with a number. Something like "5 more things" will also evaluate to true in PHP.

Now imagine if a website used this kind of functionality in the login process. The web application takes the username and password and deserializes that data to make a comparison to an actual password.

Imagine the application makes your session token like this once you’ve logged in:

O:4:"User":2:{s:8:"username";s:4:"gabe";s:12:"access_token";s:32:"967ef2eb34634ba418db94dab610ba6f";}

If the 32-character long string is a hash of your input password that is to be compared with a hash of the real password on the back-end somewhere, we could take advantage of the PHP comparison quirk and our ability to manipulate the serialized object.

As we described earlier, integers being compared to strings will often result in strange results which becomes more strange when comparing to 0. If we compare zero with any string, it will evaluate to a true statement, so if we change our password from a 32-character string into the integer zero, we could bypass the login check and potentially log into anyone else’s account so long as we know their name like this:

O:4:"User":2:{s:8:"username";s:4:"jake";s:12:"access_token";i:0;}

This would compare the jake user’s password hash with zero, which will evaluate to true, giving us a cookie that is effectively meant for them.

As you might imagine, this is harder with binary data types and less human-readable formats.

Using Application Functionality
#

Sometimes applications will use the data from a deserialized object to perform some action, like deleting a certain file or other piece of data.

Imagine a web application that deletes our account’s profile picture when we delete our account by reading the profile picture’s path from a serialized object like this:

O:4:"User":3:{s:8:"username";s:4:"gabe";s:12:"access_token";s:32:"t06qyrson1g7o877xnh8st1kw8p1kfvx";s:11:"avatar_link";s:18:"users/gabe/avatar";}

If we know the path of another important file, we might be able to delete it by modifying this object:

O:4:"User":3:{s:8:"username";s:4:"gabe";s:12:"access_token";s:32:"t06qyrson1g7o877xnh8st1kw8p1kfvx";s:11:"avatar_link";s:30:"users/jake/pls_dont_selete.txt";}

This would delete the other user’s important file when we delete our own account. This isn’t really an issue with deserialization itself, but more the amount of trust being placed in these objects by the application’s logic.

Magic Methods
#

Magic methods are certain methods, often used in object-oriented programming languages, that are invoked automatically. These are usually indicated with double-underscores around the method’s name.

For example, when you add numbers together in python using the + operator, the __add__() method is called. This is most commonly seen with constructor methods that build an object in a certain class, but developers can make whatever magic methods they want.

This can come in handy for attackers because we might be able to get certain code executed by invoking one of these magic methods.

Object Injection
#

We’ve already made changes to attributes and data types, but if we can add a whole new object our capabilities might reach even further. In object-oriented programing, an object’s methods are determined by the class it belongs to - so changing the class of an object we control is even more lucrative.

Deserialization methods don’t usually check that is being deserialized, so we might be able to pass in an arbitrary class and object that get interpreted in a way that allows for code execution or unexpected errors. This kind of exploit would be easiest to develop if we are able to read source code, as we would have more details about the implementation of certain methods and objects.

Gadget Chains
#

In this context, a gadget is a snippet of code that can help us achieve a certain action. Even though we only control some input data, we can construct our payloads in such a way that it takes advantage of multiple sensitive gadgets in an application with the intent to cause the most damage.

Manually identifying these gadget chains can be difficult though, because they are usually in the form of magic methods. Thankfully, there are pre-built gadget chains that we can use to more easily exploit these vulnerabilities.

For Java, ysoserial is a really useful tool and for PHP, phpggc works well.

HTB - Celestial
#

We start this machine with a port scan and see that NodeJS is being used on port 3000. When we visit the page we see the following text:

Hey Dummy 2 + 2 is 22

If we look at our cookie, it seems to be base64 encoded:

eyJ1c2VybmFtZSI6IkR1bW15IiwiY291bnRyeSI6IklkayBQcm9iYWJseSBTb21ld2hlcmUgRHVtYiIsImNpdHkiOiJMYW1ldG93biIsIm51bSI6IjIifQ

If we decode this, we see what looks like a JavaScript object:

{"username":"Dummy","country":"Idk Probably Somewhere Dumb","city":"Lametown","num":"2"}

The only thing of interest here is the number, because it seems to relate with the front-end page contents. Let’s change the number and re-encode the cookie and apply it to our session to see if the application behavior changes.

# I change the number:

{"username":"Dummy","country":"Idk Probably Somewhere Dumb","city":"Lametown","num":"24"}

# Then, I base64 encode it

eyJ1c2VybmFtZSI6IkR1bW15IiwiY291bnRyeSI6IklkayBQcm9iYWJseSBTb21ld2hlcmUgRHVtYiIsImNpdHkiOiJMYW1ldG93biIsIm51bSI6IjI0In0=

Applying this to our session results in the following response by the web application:

Hey Dummy 24 + 24 is 2424

So, it seems like our user input is being deserialized and interpreted by the web application in some way.

We don’t have any source code access so we will need to learn about NodeJS deserialization attacks. One google search reveals a pretty solid article here that I’ll try to summarize.

We can create an object and try to get it interpreted by the program when deserialization occurs. We want remote code execution so first we would want to make an object capable of running some shell command and serialize it, then see if the command is run upon deserialization.

Here is our possible serialized object:

{"rce":"_$$ND_FUNC$$_function (){\n \t require('child_process').exec('ls /',
function(error, stdout, stderr) { console.log(stdout) });\n }()"}

This uses IIFE, which seems similar to magic methods, to execute the ls command when the object is deserialized. Thankfully, the blog post has a python script that we can use to make a reverse shell.

We can base64 encode the output of the script and swap it for our cookie, once we’ve done this we will get a shell on the machine as the sun user.

How Did This Happen?
#

Usually, these issues arise because of too much trust being placed on user-controllable inputs once they are deserialized. Ideally, you wouldn’t deserialize user inputs at all and would use an alternative method to modify the objects.

Modern websites also use a variety of libraries and dependencies that make managing all of your classes and objects difficult as a developer. This wide use of objects and classes makes the attack surface larger and makes it almost impossible for a developer to anticipate the flow of malicious data and secure the site.

The impact for deserialization can be severe because it open up the attack surface to other deserialization type attacks. This can be as trivial as information exposure or as serious as remote code execution.

Prevention
#

Generally speaking, avoid deserializing user input whenever possible and only serialize data from trusted sources. If for some reason that is not possible in your application, then you’d want to incorporate checks to ensure the data hasn’t been tampered with.

Instead of using generic deserialization features, try to implement your own class-specific serialization method to control which specific fields are exposed.

Keep in mind that the vulnerability isn’t so much with deserialization, but the deserialization of user input, so try to handle that before tackling the larger gadget chain attack surface.

Related

DOM-Based Vulnerabilities - Applied Review
·10 mins
web BSCP
What is the DOM? # The document object model is a web browser’s representation of the elements on the page.
WebSockets - Applied Review
·5 mins
web BSCP
What is a Web Socket? # WS (WebSockets) are widely used in modern web applications because they can initiate long-lived sessions over HTTP with asynchronous communication in both directions.
API Testing - Applied Review
·8 mins
web BSCP
What is an API? # Application Programming Interfaces (APIs) allow for different software systems and applications to share data.