Server Side Template Injection in Django with Bearer Tokens
Server Side Template Injection in Django with Bearer Tokens — how this specific combination creates or exposes the vulnerability
Server Side Template Injection (SSTI) in Django occurs when an attacker can inject template code that is later evaluated by the Django template engine. When this vulnerability intersects with Bearer Token authentication, the risk expands because tokens are often handled in request headers, logs, and error messages, and can be leaked through template rendering or insecure debug output.
Django’s built-in template engine is generally sandboxed and does not allow arbitrary Python execution by default. However, if a developer uses string.Template, Jinja2, or a third-party template library that supports dynamic code execution, and that library is invoked with data influenced by an attacker, SSTI becomes possible. If an API endpoint accepts a Bearer token in the Authorization header and that token is mistakenly interpolated into a rendered template (for example, for logging or dynamic context), an attacker can supply a crafted token containing template syntax to achieve injection.
A realistic scenario: an API validates a Bearer token by decoding its payload and passes the claims into a template for diagnostic or branding purposes. If the claims are rendered unsafely (e.g., using |safe or a custom filter that does not escape), an attacker’s token payload like {% include "malicious_snippet" %} can lead to arbitrary template execution. This can expose sensitive data, trigger server-side requests, or leak tokens through error messages or logs. Because Bearer tokens are often long-lived and high-value, their exposure via SSTI compounds the impact, potentially enabling privilege escalation or further attacks.
Middleware or custom decorators that extract the token and inject it into the template context without strict validation or escaping create the bridge between authentication and template rendering. For instance, adding token_payload to the template context and then rendering user-influenced input without escaping is a common pattern that turns a benign token into an injection vector.
Bearer Tokens-Specific Remediation in Django — concrete code fixes
To mitigate SSTI when using Bearer Tokens in Django, ensure tokens are never directly interpolated into templates and that any data derived from tokens is treated as untrusted. Use Django’s built-in template escaping, avoid the |safe filter on token-derived content, and validate and sanitize all inputs.
Example: Unsafe token handling
import base64
import json
from django.http import JsonResponse
def unsafe_view(request):
auth = request.headers.get("Authorization", "")
if auth.startswith("Bearer "):
token = auth.split(" ")[1]
try:
# Unsafe: payload is passed directly into template context
payload = base64.urlsafe_b64decode(token + "==").decode()
claims = json.loads(payload)
except Exception:
claims = {}
# Dangerous: claims may contain attacker-controlled values used in template
return render(request, "debug.html", {"claims": claims})
return JsonResponse({"error": "Missing token"}, status=401)
Example: Safe remediation
import base64
import json
from django.http import JsonResponse
from django.utils.html import escape
def safe_view(request):
auth = request.headers.get("Authorization", "")
if auth.startswith("Bearer "):
token = auth.split(" ")[1]
try:
decoded = base64.urlsafe_b64decode(token + "==").decode()
claims = json.loads(decoded)
except Exception:
claims = {}
# Safe: escape any data derived from the token before passing to template
safe_claims = {k: escape(v) if isinstance(v, str) else v for k, v in claims.items()}
# Do not pass raw token or eval-able content to the template
return render(request, "debug.html", {"claims": safe_claims})
return JsonResponse({"error": "Missing token"}, status=401)
Example: Using Django forms/validators instead of manual token parsing
from django import forms
from django.http import JsonResponse
class BearerTokenForm(forms.Form):
authorization = forms.CharField(required=False)
def clean_authorization(self):
value = self.cleaned_data["authorization"]
if not value or not value.startswith("Bearer "):
return value
token = value.split(" ", 1)[1]
if len(token) < 10:
raise forms.ValidationError("Invalid token")
# Do not decode or render token content in templates
return token
def validated_view(request):
form = BearerTokenForm(request.headers)
if form.is_valid():
# Use token only for authentication, not for template rendering
return JsonResponse({"status": "ok"})
return JsonResponse(form.errors, status=400)
Additional hardening steps
- Never log raw Bearer tokens or include them in error messages.
- Use Django’s
JsonResponseand API views rather than rendering HTML templates for token-based endpoints. - Apply strict Content Security Policy (CSP) headers to reduce the impact of any injected content.
- If you must render token metadata, treat it as untrusted input and always escape strings with
escape()or use Django’s auto-escaping in templates.