Sandbox Escape in Django
How Sandbox Escape Manifests in Django
In Django, a sandbox escape typically occurs when untrusted data is allowed to influence the template rendering engine or when deserialization mechanisms (such as pickle) are used without proper validation. The Django template language, while designed to be safe, still provides access to Python object attributes and methods. If an attacker can control what is rendered inside a {{ … }} block, they can traverse object graphs to reach dangerous built‑ins like os.system or subprocess.Popen and execute arbitrary commands.
One common vulnerable pattern is a view that takes a user‑supplied string and passes it directly to Django’s Template class:
from django.template import Template, Context
from django.http import HttpResponse
def render_user_template(request):
user_template = request.GET.get('t', '') # attacker‑controlled
tmpl = Template(user_template) # DANGEROUS: no sandbox
return HttpResponse(tmpl.render(Context({})))
Because Template compiles the string into a Django template, the attacker can inject payloads such as:
{{ '__class__.__mro__[2].__subclasses__()' }}
{{ ''.__class__.__mro__[1].__subclasses__()[XXX].__init__.__globals__['os'].popen('id') }}
Another vector is the use of pickle for caching or session storage. If an endpoint accepts a pickle‑serialized blob and deserializes it with pickle.loads (or uses Django’s cache backend configured with the pickle serializer), an attacker can craft a payload that executes code during deserialization:
import pickle
import os
class Exploit:
def __reduce__(self):
return (os.system, ('id',))
malicious = pickle.dumps(Exploit())
# sending this to a view that does pickle.loads(data) will run `id`.
These patterns map to the OWASP API Security Top 10 item A1:2023 – Broken Object Property Authorization, because they allow property (or method) access that should be restricted.
Django‑Specific Detection — How to Identify This Issue (Including middleBrick)
Detecting a sandbox escape requires probing for the two main vectors: template injection and unsafe deserialization.
- Template injection probing: Send payloads that attempt to read Python internals. A safe test is to request a parameter that will be rendered inside a template and include the expression
{{ ''.__class__.__mro__[1].__subclasses__() }}. If the response contains a list of subclasses (or any Python object representation), the template engine is exposing internal types, indicating a potential sandbox escape. - Unsafe deserialization probing: Transmit a minimal pickle payload that calls a harmless function (e.g.,
os.uname) and observe side‑effects such as changes in response headers, error messages, or timing differences. Because the payload does not cause damage, it only confirms that the endpoint will deserialize arbitrary pickle data.
middleBrick’s unauthenticated black‑box scanner includes checks that align with these techniques. When you submit a URL, middleBrick:
- Tests the Input Validation check by fuzzing parameters with template‑injection strings and looking for reflective output that reveals Python internals.
- Tests the Data Exposure and Property Authorization checks by attempting to deserialize pickle‑like payloads and monitoring for unexpected behavior (e.g., changes in response size or error codes).
- Reports any finding with severity, the affected parameter, and remediation guidance — without fixing the issue itself.
Example CLI usage:
middlebrick scan https://api.example.com/render-template
The resulting JSON report will list a finding such as:
{
"parameter": "t",
"issue": "Potential template sandbox escape",
"severity": "high",
"remediation": "Avoid rendering user‑supplied strings as Django templates. Use only pre‑approved template files and enable auto‑escaping."
}
Similarly, if a cache endpoint accepts a blob, middleBrick may flag:
{
"parameter": "data",
"issue": "Unsafe deserialization (pickle)",
"severity": "critical",
"remediation": "Switch to JSON‑based serialization for cache/sessions and validate any incoming binary data."
}
These findings give developers a concrete starting point for remediation.
Django‑Specific Remediation — Code Fixes Using Native Features
The safest approach is to eliminate the risky patterns entirely and rely on Django’s built‑in safeguards.
1. Prevent template injection
Never feed user‑controlled strings to django.template.Template. Instead, load templates from the filesystem using Django’s template loader, which only reads files from trusted directories.
Vulnerable:
from django.template import Template, Context
from django.http import HttpResponse
def bad_view(request):
tmpl = Template(request.GET.get('t', ''))
return HttpResponse(tmpl.render(Context({})))
Fixed:
from django.template.loader import get_template
from django.http import HttpResponse
def safe_view(request):
# Only allow a whitelist of template names
tname = request.GET.get('t', 'default.html')
if tname not in {'default.html', 'welcome.html'}:
tname = 'default.html'
tmpl = get_template(tname) # loads from TEMPLATES DIRS
return HttpResponse(tmpl.render({}))
Additionally, ensure that Django’s auto‑escaping is enabled (the default). If you must mark a string as safe, do so only after rigorous validation:
from django.utils.safestring import mark_safe
from django.utils.html import escape
def safe_mark(text):
# Allow only alphanumeric and spaces
if all(c.isalnum() or c.isspace() for c in text):
return mark_safe(text)
return escape(text)
2. Replace pickle with JSON for serialization
Django’s cache, sessions, and serializers can be configured to use JSON, which does not allow arbitrary code execution.
settings.py – sessions:
SESSION_SERIALIZER = 'django.contrib.sessions.serializers.JSONSerializer'
settings.py – cache (using locmem as example):
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'SERIALIZER': 'django.core.cache.serializers.json.JSONSerializer',
}
}
If you must accept binary data from clients, validate it against a strict schema (e.g., using jsonschema) before any processing, and never pass it to pickle.loads.
3. Leverage Django’s built‑in security middleware
Enable django.middleware.security.SecurityMiddleware and set SECURE_CONTENT_TYPE_NOSNIFF, SECURE_BROWSER_XSS_FILTER, and SECURE_HSTS_SECONDS to reduce the impact of any remaining injection vectors.
By combining these practices — restricting template sources, avoiding pickle, and relying on Django’s auto‑escaping and JSON serializers — you eliminate the pathways that lead to a sandbox escape while still delivering full functionality.