Pickle Deserialization
You have probably heard the warning, do not unpickle untrusted data, and you probably nodded along because it makes sense, pickle is dangerous, we know this.
But here is the problem, most developers do not realize where pickle is actually being used in their applications, they think of pickle as something they explicitly choose to use, a deliberate function call they would never make on user input, and they are wrong.
Pickle is buried in your session cookies, your message queues, your caching layers, and your ML pipelines, it is running in places you never explicitly put it, and if an attacker can reach any of those sinks, they can execute code on your server.
This is the hidden pickle problem, and it is bigger than most teams realize.
Important Disclaimer
This article is intended for educational and defensive purposes only, the techniques described here are shared to help security professionals understand Python deserialization risks so they can better protect their systems.
Do not use these techniques against systems you do not own or do not have explicit written permission to test, unauthorized testing is illegal in most jurisdictions.
The author assumes no liability for any damages, legal consequences, or other outcomes resulting from the use or misuse of this information, always obtain proper authorization before conducting any security testing, and stay legal, stay ethical, stay responsible.
What Is a Pickle Sink?
A sink is any place in your application where data gets deserialized, when that deserialization uses pickle, it becomes a pickle sink, and every pickle sink is a potential remote code execution vulnerability.
The danger is not theoretical, pickle.loads() invokes the reduce method on objects during deserialization, and reduce can return any callable, attackers abuse this to call os.system, subprocess.Popen, or any other function that runs commands.
Here is the simplest possible example.
import pickle
import os
class Evil:
def __reduce__(self):
return (os.system, ("whoami",))
payload = pickle.dumps(Evil())
pickle.loads(payload) # runs "whoami" on the serverThat is the entire attack, if an attacker can get their payload into a pickle sink, they run code, and the code runs with the privileges of your application.
The question is not whether pickle is dangerous, the question is where pickle sinks exist in your application, and the answer is more places than you think.
Sink 1: Session Cookies
This is the most common pickle sink, and it is often the most dangerous.
Many Python web frameworks serialize session data into a cookie, and some of them use pickle to do it, Flask, for example, used pickle for session serialization by default in older versions, and while modern Flask defaults to JSON, a lot of applications still use pickle-based session serializers.
How the attack works:
- The attacker identifies that your application uses pickle-based session cookies, they can often tell by looking at the cookie format, base64-encoded pickle data starts with specific bytes
- They craft a malicious pickle payload that executes a reverse shell or downloads additional malware
- They base64-encode the payload and set it as their session cookie
- When your application deserializes the cookie to load the session, the payload executes
Why signing does not always save you:
Flask signs session cookies with a secret key, so an attacker cannot simply tamper with a cookie, they need the signing key first, but signing keys leak, they end up in debug endpoints, misconfigured environment variables, source code repositories, and container images, and once the key leaks, the signing protection evaporates.
There is also a subtler attack, if an attacker can write a file to the Flask session store directory (which happens through unprotected file upload features or path traversal vulnerabilities), they can plant a malicious pickle file at a predictable session path and trigger deserialization when any request arrives with the corresponding session cookie.
Real-world example:
The pyLoad project had a vulnerability (CVE-2026-33509 and the incomplete fix CVE-2026-35464) where a user with SETTINGS and ADD permissions could redirect downloads to the Flask filesystem session store, plant a malicious pickle payload as a predictable session file, and trigger code execution when any HTTP request arrived with the corresponding session cookie, the attack did not require the signing key because the attacker was writing the pickle file directly to the session store.
How to fix it:
- Check your framework's session serializer, if it uses pickle, switch to JSON or another safe format
- Flask: set SESSION_SERIALIZER to a JSON-based serializer
- Django: Django's session backend uses JSON by default, but check if you have customized it
- Rotate your secret keys regularly, and never commit them to source control
Sink 2: Message Queues
This is where things get really interesting, because message queues are trusted infrastructure, and that trust is exactly what makes them dangerous.
When you configure a Python application to use a message queue like Redis for inter-server communication, messages sent between servers are often encoded using pickle, the receiving server assumes the message is trusted because it came from the queue, and it deserializes it immediately.
The vulnerability is that the queue itself can be compromised, if an attacker gains access to the message queue (through weak authentication, network exposure, or a separate vulnerability), they can inject a malicious pickle payload, and every server that consumes messages from that queue will execute it.
Real-world example:
Vulnerability CVE-2025-61765 impacted versions of python-socketio before 5.14.0 where in case Socket.IO server was set up to utilize Redis for communication between servers, messages had been encoded with pickle. An attacker that has access to the message queue could craft a malicious pickle payload that would execute arbitrary Python code upon decoding by means of the reduce function, the fix was to update the version to 5.14.0, which removed pickle and switched to JSON encoding.
Why this is so dangerous:
Message queues are often deployed with weak security, Redis, for example, is frequently deployed without authentication in internal networks, the assumption is that internal traffic is trusted, and that assumption is wrong.
An attacker who compromises a single web server can pivot to the message queue, and from there, they can execute code on every server that connects to that queue, a single breach becomes a full-scale compromise.
How to fix it:
- Check if your message queue library uses pickle for serialization, if it does, upgrade to a version that uses JSON or another safe format
- Never expose your message queue to untrusted networks
- Use authentication and encryption for all message queue connections
- Monitor message queue traffic for unusual payloads
Sink 3: Caching Layers
Caching layers like Redis and Memcached are commonly used to store serialized objects, and guess what, many caching libraries use pickle by default.
Django's cache backend, for example, can use pickle to serialize cached objects, if an attacker can inject data into the cache (through a separate vulnerability, or by compromising the cache server directly), they can plant a malicious pickle payload, and when the application retrieves that object from the cache, the payload executes.
Real-world example:
If the attacker was able to get hold of the Redis server that Celery uses as its message broker in versions earlier than 4.0, the attacker could inject a malicious pickle payload into the queue which executes arbitrary code on the worker upon fetching the task.
How to fix it:
- Make sure to configure a secure cache serializer, Django’s caching system supports a serializer, use JSON not pickle
- Celery: Use JSON serialization for messages, it is the default now in modern Celery versions
- Protect your cache with authentication and isolation
Sink 4: ML Model Loading
This is a newer sink, but it is growing fast.
Machine learning models are often serialized with pickle, PyTorch's default torch.load uses pickle, and many pre-trained models are distributed as pickle files.
When you load a model from an untrusted source, you are deserializing a pickle file, and if that file contains a malicious reduce payload, it executes when you load the model.
Real-world example:
The nullifAI disclosure in 2025 documented malicious models on Hugging Face that evaded Picklescan checks by using broken PyTorch archives, the attacker corrupted the archive enough to bypass the scanner, then relied on PyTorch's permissive loader to execute the payload anyway, the model executed a reverse shell when loaded.
How to fix it:
- Use SafeTensors instead of pickle to serialize the model as it is developed to specifically remove the code execution vector
- Scan all models before using them, make use of ModelScan, Fickling, and Veritensor
- Never use any model from an untrusted source without scanning it
Sink 5: API Parameters and Form Fields
This is the sink that developers create themselves, sometimes without realizing it.
If you have an API endpoint that accepts a serialized object (base64-encoded pickle data, for example), and you deserialize it without validation, you have created a pickle sink.
This is less common than session cookies or message queues, but it happens, especially in internal APIs where developers assume the caller is trusted.
How to fix it:
- Never accept serialized objects from untrusted sources
- If you must accept serialized data, use a safe format like JSON
- If you must use pickle, sign the payloads and verify signatures before deserialization, but remember that signing only works as long as the key stays secret
The Hidden Sink Problem
The core issue is visibility, most teams do not have a complete inventory of where pickle is being used in their applications, it is buried in dependencies, it is configured in framework defaults, it is sitting in a requirements.txt file and nobody noticed.
You cannot secure what you do not know exists.
How to find your pickle sinks:
1. Grep your codebase
Look for pickle.load, pickle.loads, cPickle.load, and cPickle.loads. These are the sinks. You have made these directly.
2. Check your dependencies
Look at the libraries you use, do any of them use pickle for serialization, Redis clients, Celery, Flask session serializers, ML frameworks, message queue libraries, check their documentation and their source code.
3. Audit your configuration
Check your framework settings, Flask's SESSION_SERIALIZER, Django's CACHE serializer, Celery's task_serializer, these settings often default to pickle in older versions.
4. Scan your models and artifacts
If you load ML models, scan them before loading, use automated tools that detect pickle opcodes.
5. Monitor runtime behavior
Use runtime application self-protection (RASP) or eBPF-based tools to detect pickle deserialization events, if you see pickle.loads being called in production, investigate, it might be legitimate, or it might be an attacker exploiting a sink you did not know about.
Comparison Table: Pickle Sinks and Risk Levels
|
Sink |
Default Pickle Usage? |
Attack Vector |
Risk Level |
|
Session Cookies |
Yes (Flask older versions) |
Tampered cookie, planted session file |
Critical |
|
Message Queues |
Yes (Socket.IO older versions) |
Queue compromise, injection |
Critical |
|
Caching Layers |
Yes (Django cache, Celery older versions) |
Cache injection, broker compromise |
High |
|
ML Model Loading |
Yes (PyTorch default) |
Malicious model file |
High |
|
API Parameters |
Developer choice |
Direct payload injection |
Medium |
How to Defend Against Pickle Sinks
Defending against pickle sinks requires a layered approach.
1. Eliminate Pickle Where Possible
The best defense is to not use pickle at all, switch to JSON, MessagePack, or Safetensors, these formats do not execute code during deserialization.
2. If You Must Use Pickle, Restrict It
If you cannot eliminate pickle, restrict what it can do, use a RestrictedUnpickler that only allows safe classes, this blocks malicious payloads even in files you did not fully verify.
import pickle
import io
class SafeUnpickler(pickle.Unpickler):
def find_class(self, module, name):
if module == "builtins" and name in ("dict", "list",
"str", "int"):
return super().find_class(module, name)
raise pickle.UnpicklingError(f"Blocked: {module}.{name}")3. Signing and Verification
If you have no other choice and must resort to using pickle for data storage, sign the objects and validate the signatures before you deserialize them; however, note that this solution will work only if the key is private.
4. Scanning Before Deserialization
Tools such as ModelScan, Fickling, or Veritensor can scan the pickle files before you load them; such tools decompile the bytecode and detect malicious opcodes.
5. Monitor for Deserialization
Use runtime monitoring to detect pickle deserialization events, if you see unexpected pickle.loads calls, investigate immediately.
6. Secure Your Infrastructure
Message queues, caching layers, and session stores are infrastructure, treat them like infrastructure, enable authentication, isolate them from untrusted networks, and monitor them for unusual activity.
Quick Reference: Pickle Sink Defense Checklist
|
Defense Layer |
Action |
|
Session Cookies |
Switch to JSON serializer, rotate secret keys |
|
Message Queues |
Upgrade to JSON encoding, enable authentication |
|
Caching Layers |
Use JSON serializer, secure cache infrastructure |
|
ML Models |
Use SafeTensors, scan before loading |
|
API Parameters |
Never accept pickle from untrusted sources |
|
Codebase |
Grep for pickle.load and pickle.loads |
|
Dependencies |
Audit libraries for pickle usage |
|
Runtime |
Monitor for deserialization events |
The Bottom Line
Pickle sinks are everywhere, they are in your session cookies, your message queues, your caching layers, and your ML pipelines, and most teams do not have a complete inventory of where they exist.
The attack surface is real, CVE-2025-61765 showed how a compromised message queue can lead to full-scale compromise, and the nullifAI disclosure showed how malicious models bypass scanners and execute code on load.
The defense is clear, eliminate pickle where possible, restrict it where you cannot, scan before loading, and monitor for deserialization, do not assume that trusted infrastructure is safe, and do not assume that you know where all your pickle sinks are.
Find them before attackers do.
FAQ Section
What is a pickle sink?
A pickle sink is any place in an application where data is deserialized using Python's pickle module, if an attacker can control the input to that sink, they can execute arbitrary code.
Where are pickle sinks most commonly found?
Session cookies, message queues, caching tiers, loading of machine learning models, and API parameters are typical pickle sinks in Python programs.
Is Flask session cookie susceptible to pickle deserialization attack?
Flask used pickle-based session serialization in its earlier releases but now uses JSON by default. Some applications, however, might be using pickle-based serializers.
How does CVE-2025-61765 work?
CVE-2025-61765 affected python-socketio when using Redis for inter-server communication, an attacker with access to the message queue could inject a malicious pickle payload that executed code on every server that consumed messages from the queue.
Can I use pickle safely?
Pickle is safe when you fully control the input and the environment, if you must use pickle, sign your payloads, use a RestrictedUnpickler, and never deserialize data from untrusted sources.
What is the best alternative to pickle?
JSON and MessagePack are safe alternatives for data serialization, SafeTensors is the safe alternative for ML model serialization.