Server Side Template Injection in Django with Mutual Tls
Server Side Template Injection in Django with Mutual Tls — how this specific combination creates or exposes the vulnerability
Server Side Template Injection (SSTI) in Django occurs when an attacker can control a template variable or expression that is evaluated by the server-side rendering engine, such as Django’s built-in template language or Jinja2 when integrated. In a typical Django view, if user-supplied input is directly passed into a template context and rendered without proper escaping or validation, an attacker may inject template code that leads to remote code execution (RCE), information disclosure, or authentication bypass.
Mutual Transport Layer Security (Mutual TLS) is used to authenticate both the client and the server using certificate-based authentication. While Mutual TLS strengthens channel security and ensures that only authorized clients can reach the endpoint, it does not protect the application logic or input handling within the authenticated session. A common misconfiguration is to assume that Mutual TLS alone secures business logic, leading developers to skip input validation and output encoding for authenticated requests.
When Mutual TLS is enforced, an attacker who possesses a valid client certificate can authenticate successfully and then probe authenticated endpoints for SSTI. For example, an API or web view that accepts a user-controlled string to render a template—such as a dashboard title, email snippet, or configuration preview—may pass that string directly into the template context. If the template engine evaluates the string as code (e.g., through custom filters, unsafe includes, or dynamic variable access), the attacker can inject payloads like {{ variable.__class__.__mro__ }} in Jinja2 or exploit Django template filters to read files or execute commands.
Even when Mutual TLS is correctly implemented, the risk surface remains if the application treats authenticated inputs as inherently safe. Django’s template system provides some escaping for HTML by default, but developers can disable auto-escaping using |safe or by using the mark_safe function. If an SSTI-vulnerable parameter is combined with relaxed escaping and Mutual TLS authentication, the impact is severe: an authenticated client can execute arbitrary Python code on the server.
Consider a scenario where a Django view uses Mutual TLS and accepts a name parameter from a client certificate–bound request to personalize a rendered template. If the view does not validate or sanitize name and passes it to the template, an attacker could supply a payload such as {% include "evil_template" %} if the application allows dynamic template selection. Even without dynamic includes, nested filters and attribute access can lead to sensitive data exposure, as demonstrated by payloads like {{ request|attr:"__dict__" }}.
To identify such issues during a scan, middleBrick tests authenticated attack surfaces—including those protected by Mutual TLS—by submitting crafted inputs that probe template injection vectors, then analyzing responses for unintended code execution or data leakage. This ensures that security checks account for authenticated contexts where developers might otherwise assume safety due to transport-layer protections.
Mutual Tls-Specific Remediation in Django — concrete code fixes
Remediation focuses on strict input validation, output encoding, and avoiding dynamic template evaluation regardless of Mutual TLS status. The following practices and code examples demonstrate how to secure Django applications when Mutual TLS is used.
1. Validate and sanitize all user-controlled inputs
Even with Mutual TLS, treat every client-supplied value as untrusted. Use Django forms or serializers with explicit validation. For example, if a certificate-bound field such as common_name is used, validate it against an allowlist:
import re
from django import forms
class SafeNameForm(forms.Form):
name = forms.CharField()
def clean_name(self):
value = self.cleaned_data['name']
# Allow only alphanumeric characters and limited punctuation
if not re.match(r'^[A-Za-z0-9_\-\s]+$', value):
raise forms.ValidationError('Invalid name format')
return value
2. Use auto-escaping and avoid marking unsafe content
Never use the |safe filter or mark_safe on untrusted data. Rely on Django’s default auto-escaping in templates. If you must include dynamic content, use the escape filter explicitly:
{{ user_supplied_data|escape }}
3. Avoid dynamic template selection and custom filters that execute code
Do not allow client input to determine template names or paths. If you must load templates dynamically, restrict choices to a predefined set:
from django.shortcuts import render
ALLOWED_TEMPLATES = {
'profile': 'profile.html',
'settings': 'settings.html',
}
def profile_view(request):
template_key = request.GET.get('view', 'profile')
template_name = ALLOWED_TEMPLATES.get(template_key, 'profile.html')
return render(request, template_name, {'user': request.user})
4. Configure Jinja2 with a sandboxed environment if used
If integrating Jinja2, use a sandboxed environment and avoid exposing dangerous functions:
from jinja2 import SandboxedEnvironment, select_autoescape
env = SandboxedEnvironment(autoescape=select_autoescape(['html', 'xml']))
env.filters['safe_upper'] = lambda x: x.upper()
# Render with strict control over globals and filters
template = env.get_template('message.html')
output = template.render(content=user_data)
5. Enforce Mutual TLS without relaxing input checks
Ensure your web server (e.g., Nginx, Apache) or reverse proxy enforces client certificate verification. In Django, you can still read the client certificate subject to apply business rules, but never skip validation:
import ssl
from django.http import HttpResponseBadRequest
def ssl_client_cert_view(request):
cert = request.META.get('SSL_CLIENT_CERT')
if not cert:
return HttpResponseBadRequest('Client certificate required')
# Perform additional validation on certificate fields if needed
# Do not rely on cert presence alone to authorize unsafe operations
return render(request, 'secure.html', {})
6. Security testing and scanning
Use tools like middleBrick to scan authenticated endpoints, including those protected by Mutual TLS. Configure the scanner to present valid client certificates during testing so that authenticated attack surfaces are exercised. Remediation should be verified by confirming that malicious template payloads are neutralized and that no sensitive data is exposed through template evaluation.
| Remediation Action | Why It Matters | Example Implementation |
|---|---|---|
| Input validation with allowlists | Prevents injection of template syntax | Regex check on name fields |
| Disable auto-escaping overrides | Avoids accidental code execution | No use of |safe on user data |
| Restrict template selection | Blocks path traversal or remote inclusion | Map known-safe template keys |
| Sandboxed Jinja2 environments | Limits execution surface if Jinja2 is used | SandboxedEnvironment with limited filters |
| Do not trust Mutual TLS alone | Transport auth ≠ logic safety | Apply validation even for cert-bound requests |