HIGH server side template injectiondjangomutual tls

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 ActionWhy It MattersExample Implementation
Input validation with allowlistsPrevents injection of template syntaxRegex check on name fields
Disable auto-escaping overridesAvoids accidental code executionNo use of |safe on user data
Restrict template selectionBlocks path traversal or remote inclusionMap known-safe template keys
Sandboxed Jinja2 environmentsLimits execution surface if Jinja2 is usedSandboxedEnvironment with limited filters
Do not trust Mutual TLS aloneTransport auth ≠ logic safetyApply validation even for cert-bound requests

Frequently Asked Questions

Does Mutual TLS prevent Server Side Template Injection in Django?
No. Mutual TLS secures the transport channel and client authentication, but it does not prevent SSTI if the application improperly handles user-controlled input within templates. Always validate and encode inputs regardless of TLS protections.
Can middleBrick scan endpoints protected by Mutual TLS?
Yes. middleBrick can be configured to present client certificates during scans so that authenticated attack surfaces—protected by Mutual TLS—are tested for SSTI and other vulnerabilities.