Data Exposure in Django with Mutual Tls
Data Exposure in Django with Mutual TLS — how this specific combination creates or exposes the vulnerability
Mutual TLS (mTLS) ensures that both the client and the server present certificates during the TLS handshake. In Django, enabling mTLS typically involves configuring the web server (such as Nginx or Apache) or an API gateway to require client certificates and then passing the verified client identity to the application. When mTLS is used but the Django application does not properly validate or handle the client certificate chain, sensitive information can be exposed or incorrectly trusted.
One common risk is treating the presence of a client certificate as sufficient authorization without verifying its details. For example, a developer might rely only on the existence of SSL client certificate fields exposed by the web server (e.g., SSL_CLIENT_S_DN or SSL_CLIENT_VERIFY) without checking the certificate’s issuer, validity period, or revocation status. If an attacker compromises a client certificate or the server trusts any presented certificate, sensitive data tied to that identity could be exposed through endpoints that return user-specific information without additional checks.
Another exposure scenario occurs when Django logs or error messages inadvertently include data derived from the client certificate, such as distinguished names or serial numbers. If logs are aggregated or stored without proper access controls, this information can aid an attacker in mapping identities to users or systems. The data exposure check in middleBrick tests for these kinds of accidental disclosures by probing endpoints that may return or log sensitive certificate-derived data.
In a typical Django deployment terminating TLS at the proxy, the application may receive the client certificate information via HTTP headers like HTTP_X_SSL_CLIENT_CERT or similar variables set by the proxy. If Django views directly trust these headers without verifying that the proxy enforces mTLS, an attacker could spoof these headers to gain unauthorized access to data. This bypass can lead to data exposure where APIs return personal or sensitive records because the backend assumes the proxy has already authenticated the client.
middleBrick’s unauthenticated scans test the attack surface without credentials, including endpoints that may return data based on mTLS-derived claims. The scanner checks whether data exposure occurs when identity is derived from mTLS headers or certificates and whether that data is leaked through verbose errors, logs, or inconsistent authorization across endpoints.
Mutual TLS-Specific Remediation in Django — concrete code fixes
To securely handle mTLS in Django, enforce strict validation of client certificates at the proxy or gateway and avoid relying on easily spoofed headers. Treat the proxy as the source of truth for certificate validation and implement additional authorization checks inside Django based on verified identity.
First, configure your proxy (e.g., Nginx) to require valid client certificates and to map certificate fields into custom request headers only after successful verification. Example Nginx configuration:
server {
listen 443 ssl;
ssl_certificate /etc/ssl/certs/server.crt;
ssl_certificate_key /etc/ssl/private/server.key;
ssl_client_certificate /etc/ssl/certs/ca.pem;
ssl_verify_client on;
location /api/ {
proxy_set_header X-SSL-Client-Verify $ssl_client_verify;
proxy_set_header X-SSL-Client-DN $ssl_client_s_dn;
proxy_set_header X-SSL-Client-NotBefore $ssl_client_i_not_before;
proxy_set_header X-SSL-Client-NotAfter $ssl_client_i_not_after;
proxy_pass http://django_app;
}
}
In Django, create a middleware that validates the proxy verification header and extracts certificate metadata safely. Do not trust the headers if the proxy verification header indicates the client certificate was not verified.
import ssl
from django.http import HttpResponseForbidden
from django.conf import settings
class MutualTLSCertificateMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
verify = request.META.get('HTTP_X_SSL_CLIENT_VERIFY')
if verify != 'SUCCESS':
return HttpResponseForbidden('Client certificate not verified')
request.client_dn = request.META.get('HTTP_X_SSL_CLIENT_DN')
request.client_not_before = request.META.get('HTTP_X_SSL_CLIENT_NOTBEFORE')
request.client_not_after = request.META.get('HTTP_X_SSL_CLIENT_NOTAFTER')
return self.get_response(request)
Use the extracted DN or a certificate serial mapped to a user in a controlled lookup, and always re-authorize on each request. Example of mapping certificate DN to a user safely within a view:
from django.shortcuts import get_object_or_404
from .models import AuthorizedUser
def sensitive_data_view(request):
dn = request.client_dn
if not dn:
return HttpResponseForbidden('Missing client identity')
user_record = get_object_or_404(AuthorizedUser, certificate_dn=dn)
# Ensure the requesting user is allowed to access the target resource
if not user_record.can_access(request.path):
return HttpResponseForbidden('Insufficient permissions')
data = fetch_sensitive_data_for_user(user_record)
return JsonResponse(data)
Additionally, rotate certificates regularly, enforce revocation checks (CRL or OCSP) at the proxy, and ensure logs do not capture full certificate contents or private-derived identifiers. The middleware approach ensures Django only processes requests with verified client certificates and maintains explicit authorization checks aligned with mTLS identities.
Related CWEs: dataExposure
| CWE ID | Name | Severity |
|---|---|---|
| CWE-200 | Exposure of Sensitive Information | HIGH |
| CWE-209 | Error Information Disclosure | MEDIUM |
| CWE-213 | Exposure of Sensitive Information Due to Incompatible Policies | HIGH |
| CWE-215 | Insertion of Sensitive Information Into Debugging Code | MEDIUM |
| CWE-312 | Cleartext Storage of Sensitive Information | HIGH |
| CWE-359 | Exposure of Private Personal Information (PII) | HIGH |
| CWE-522 | Insufficiently Protected Credentials | CRITICAL |
| CWE-532 | Insertion of Sensitive Information into Log File | MEDIUM |
| CWE-538 | Insertion of Sensitive Information into Externally-Accessible File | HIGH |
| CWE-540 | Inclusion of Sensitive Information in Source Code | HIGH |