Sandbox Escape in Django with Mutual Tls
Sandbox Escape in Django with Mutual Tls — how this specific combination creates or exposes the vulnerability
Sandbox escape in the context of a Django application using mutual TLS (mTLS) refers to a scenario where an attacker who has compromised an upstream service or client certificate misuse can bypass intended isolation boundaries and reach internal endpoints that should remain unreachable. mTLS ensures both the client and the server present valid certificates, which Django can enforce at the webserver or proxy layer (for example, via SSLVerifyClient optional_no_ca or SSLVerifyClient require in Apache/mod_wsgi, or by validating client certs in Django middleware). However, mTLS does not automatically equate to application-level authorization, and misconfigurations can lead to sandbox escape.
One common pattern is when Django trusts client certificates to identify tenants or roles but does not enforce strict authorization checks in application code after the TLS handshake. For instance, if a developer uses mTLS to allow only certain client certificates to reach the Django app, they might assume this enforces tenant isolation. Yet, if views do not validate that the authenticated principal (derived from the certificate) is allowed to access the target resource (BOLA/IDOR checks), an attacker who obtains a valid client certificate for one tenant could traverse IDs or API paths to access another tenant’s data or administrative endpoints.
Another vector involves endpoint exposure through misconfigured proxy or load balancer rules combined with Django’s URL routing. If mTLS is terminated at the proxy and the proxy routes requests based on path patterns without revalidating authorization, a poorly scoped regex or prefix-based routing can allow an attacker to reach internal management paths (e.g., /admin/ or internal health endpoints) that the developer expected to be protected by network-level restrictions alone. Because the scanner’s checks include Authentication, BOLA/IDOR, and Property Authorization, it can surface cases where mTLS is used for access control but missing application-level checks permit traversal across tenants or privilege boundaries.
Additionally, unsafe consumption of user-supplied data after mTLS authentication can lead to server-side request forgery (SSRF) or host confusion, enabling an attacker to make the backend call internal services (e.g., metadata service at 169.254.169.254) that were not intended to be reachable from the public-facing app. The scanner’s SSRF and Inventory Management checks help detect whether certificate-based authentication is paired with insufficient validation of URLs and internal endpoints, which can culminate in a sandbox escape when internal routes become accessible through manipulated inputs.
In summary, mutual TLS in Django must be treated as a transport-level guarantee rather than an application-level policy. Relying on mTLS alone without rigorous view-level authorization, tenant validation, and input sanitization creates a pathway for sandbox escape. This is why the 12 security checks, including Authentication, BOLA/IDOR, and Property Authorization, are run in parallel to identify gaps where valid mTLS sessions can still lead to unauthorized access or data exposure.
Mutual Tls-Specific Remediation in Django — concrete code fixes
To harden Django when using mutual TLS, couple mTLS with explicit application-level controls and avoid relying on TLS identity for authorization. Below are concrete remediation steps and code examples.
- Validate and map client certificates to application principals in middleware. Extract the certificate fields and convert them into a known user/tenant context, ensuring the mapping is verified against an allowlist or directory.
import ssl
from django.conf import settings
from django.http import HttpResponseForbidden
class MutualTlsMiddleware:
"""
Example middleware that extracts the client certificate and maps it to a user.
Requires proper CA chain configuration in the webserver (e.g., Apache/Nginx).
"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Certificate appears in WSGI environment when terminated at the front door
cert_pem = request.META.get('SSL_CLIENT_CERT')
if not cert_pem:
return HttpResponseForbidden('Client certificate required')
# Map cert to user/tenant — this should use a verified lookup, not raw subject parsing
user_or_tenant = self._map_certificate_to_tenant(cert_pem)
if user_or_tenant is None:
return HttpResponseForbidden('Certificate not authorized')
request.tenant = user_or_tenant # or request.user after proper auth backend
return self.get_response(request)
def _map_certificate_to_tenant(self, cert_pem):
# Implement proper validation: verify against trusted CA, extract CN/SAN,
# and map to tenant in the database. Return None if not found.
# Placeholder: real implementation would use cryptography or ssl module.
return {'tenant_id': 'verified_tenant'}
- Enforce tenant-level authorization in every view or queryset. Even with mTLS, always scope data access to the authenticated tenant and avoid ID-based traversal without checks.
from django.shortcuts import get_object_or_404, render
from .models import TenantData
def sensitive_record(request, record_id):
tenant = request.tenant # set by middleware above
record = get_object_or_404(TenantData, pk=record_id, tenant=tenant)
return render(request, 'detail.html', {'record': record})
- Apply strict input validation and canonicalization. Validate and sanitize all inputs, especially identifiers that could be used to reach internal services or perform SSRF. Use Django forms or serializers with strict type checks and allowlists.
from django import forms
class RecordLookupForm(forms.Form):
record_id = forms.IntegerField(min_value=1, max_value=1000000)
# In view
def safe_lookup(request):
form = RecordLookupForm(request.GET)
if not form.is_valid():
return HttpResponseForbidden('Invalid parameters')
record_id = form.cleaned_data['record_id']
# proceed with tenant-scoped lookup
- Harden proxy and routing rules. Ensure that path-based routing does not inadvertently expose internal endpoints. Re-validate authorization at the application layer rather than relying on path prefix locks.
By combining mTLS client validation with robust tenant mapping, per-view authorization, and strict input validation, you reduce the risk of sandbox escape. These practices align with the scanner’s focus on Authentication, BOLA/IDOR, and Property Authorization, ensuring that valid mTLS sessions cannot bypass essential checks.