A Hands-On Introduction To Insecure Deserialization
The OWASP Top Ten 2017 lists A8:2017-Insecure Deserialization as one of the Top Ten most critical security risks to web applications. This article aims at explaining the risk posed by a similar vulnerability and a typical attack vector against it, by hands-on approach. Before understanding a vulnerability or exploiting a functionality in an application, the ﬁrst thing that should be done is to understand the core concepts behind the working of that application. So let’s begin with that.
In layman terms, an application might be using user defined data types, or what is more popularly known as classes. The
running instances of these are often known as objects. For an instance, a class user may have username and password as two data members. An object owasp is defined with
password=p455w0rd . This application may need to save these details somewhere so
that in the future, the user owasp can login
and get authenticated. This might be done by saving these details in a local file, database
or even some remote database over the network. In that case, the object must be
converted or encoded into a format that can be easily transmitted or saved. This is known as Serialization. When the application needs to fetch the object back, it just performs the reverse operation which is known as Deserialization.
Where’s the catch?
It’s indeed a very interesting approach to make data persist, by converting it into a ﬂexible form and then later converting it back when needed. But what if this conversion method is known to some bad actor? In that case, if the application tends to get the serialized data as an input from either GET or POST requests and there are no integrity checks for the object, someone can simply serialize some malicious code and try to inject it which can even lead to an RCE. Don’t believe it? Let’s do it!
Setting up the lab
OWASP Security Knowledge Framework is an open source security knowledge-base including manageable projects with checklists and best practice code examples in
multiple programming languages showing how to prevent hackers gaining access and running exploits on an application. It simply enables developers to integrate secure
coding and testing in the SDLC. The project also provides many hands-on labs in the form of Docker images to help improve our verification skills.
One such lab is KBID XXX – Deserialisation Pickle. To set it up, Docker must be installed. Run the following to pull the lab image:
Now, we need to run this image.
Setting up the lab.
The lab will be up and running at http://0.0.0.0:5000 as you can see below:
You’ll also be needing Burpsuite for this. So make sure you have configured your
Mozilla Firefox browser with the proxy to get the interception done. Also ensure that Python3 is installed.
We’ll start by getting a new user registered. For the purpose of this demonstration, we are using:
username=owaspskf & password=p455w0rd
Let’s turn the Burpsuite’s interceptor on and login with the credentials we created and the ‘Remember me’ checkbox checked.
We can see the username and password in the POST request and also the
rememberme parameter with the value on . Nothing interesting so far. Let’s hit Forward.
This surely hints us that there is an Insecure Deserialization vulnerability that will lead to an RCE (…remote shell!). Let’s click Home and see where it takes us.
Since we checked the ‘Remember me’ option, we can see a Cookie in the POST request with rememberme= field and some base64 encoded data as value. Let’s move Forwand.
…log us in. And there we go. We have already gotten the parameter to target. For sure, the user as an object is being serialized, further being encoded into a base64 string
and saved in the rememberme= field of the Cookie in the browser. This same data is
then sent back to the server during a login without credentials, where the base64 gets decoded and then deserialized to get the username and password . We still lack one
thing i.e the method being used to serialize the data. Unless or until we do not know what technology stack is running in the backend, we can not be certain about the serialization method and hence can not generate a payload to inject in the POST request.
Luckily, we have an amazing tool in our arsenal named WhatWeb whose primary goal is to identify a website. A simple scan yielded the following result:
We can now conclude that this lab uses Python3 and the most popular Python module to serialize data is pickle.
The pickle module implements binary protocols for serializing and de-serializing a Python object structure. “Pickling” is the process whereby a Python object hierarchy is converted into a byte stream, and “unpickling” is the inverse operation, whereby a byte stream (from a binary file or bytes-like object) is converted back into an object hierarchy.
We’ll now keep the serialized base64 string handy and write a simple python script to unpickle it. If it gets unpickled (or deserialized) successfully, that means we are right at this part that pickle is being used.
The code is very simple to understand. The bas64 encoded string will be input into the rememberme variable in the run-time, which will be decoded and stored in serialdata
and then deserialized by pickle and stored in deserialdata . Then the script tries to fetch the class name from the deserialized object. Pretty smooth!
Let’s run this python script with:
Oops! The script failed? Not really. We wanted to extract the name of the class and we already got that in the error i.e usr . Now, when we know that the object belongs to the class usr , let’s take our script to the second level and extract the data members of this class.
We just added a class usr and made deserialdata an object of it. The dir() function returns all properties and methods of the specified object, without the values. This means that we’ll get the data members too. So let’s just run the script again.
We can observe the data members – username and password – along with all the default properties and methods. This takes us to the last step of our deserialization
script. We just need to print out the values of these two data members. For that, modify the script as below:
And for the last time…
There we have the deserialized object with the values. Everything we did so far was just to confirm that our assumption that this lab uses pickle module for serialization and deserialization, is true. Although, it was very obvious from the name of the lab, it doesn’t happen in real life scenarios. It was important to cover this phase to eliminate any assumptions.
The only thing left is to generate a serialized base64 encoded string that triggers RCE when it gets deserialized on the application server. Then we’ll simply replace it with the string in the rememberme= field of the Cookie in the POST request. Let’s get that root! We need to write another script in Python to generate the payload as a serailized base64 encoded string. Create a new file, named payload.py with the following code:
Again, this script is also very easy to understand just like the previous one. A class payload is defined which returns a system call that actually just executes netcat to connect to our host machine’s terminal session where we’ll be listening on the same IP( lhost ) and Port( lport ) that we we’ll input into the script. An object deserialpayload is defined from this class which is then serialized and stored in serialpayload which is further encoded with base64 and stored in rememberme and get’s printed.
…we get the the final string for injection as shown below:
Note the LHOST input which is just the IPv4 address of the host machine. Do not enter 127.0.0.1 as that will look for a netcat listener in the Docker container. Now we simply need to start a netcat listener on the host machine by executing…
Let’s fire up Burpsuite again, set the intercept on, visit http://0.0.0.0:5000/login and replace rememberme= value with the payload string we generated.
We’ll hit ‘Forward’ and get back to the netcat listener to try some commands and see if we get the connection.
And there we go! But hey, wait. This doesn’t seem like a fancy shell. Let’s try to spawn the good old TTY. In the connected netcat session, execute:
This spawns a typical TTY shell.