Crlf Injection in Django with Mutual Tls
Crlf Injection in Django with Mutual Tls — how this specific combination creates or exposes the vulnerability
Crlf Injection occurs when user-controlled data is inserted into HTTP headers without proper sanitization, allowing an attacker to inject newline characters (%0D %0A) to split headers and inject malicious content. In Django, this typically manifests via the Host header, X-Forwarded-For, or custom headers that are later reflected in responses or logs. With Mutual Tls (mTLS), the server requests and validates a client certificate, adding a layer of authentication. While mTLS does not directly sanitize headers, it can change the request context: for example, front-end terminators (load balancers or API gateways) may terminate TLS and forward requests to Django with modified or trusted headers like X-Client-Cert or SSL_CLIENT_VERIFY. If Django then uses these headers in Location, Set-Cookie, or WWW-Authenticate values without validation, the presence of mTLS may create a false sense of security while the underlying header injection remains exploitable.
Consider a Django view that redirects based on a next parameter or uses X-Forwarded-For to build a custom header. An attacker can supply a crafted header such as X-Forwarded-Host: example.com%0D%0aSet-Cookie:%20session=attacker. If Django passes this to HttpResponse headers or constructs a redirect without sanitization, the injected CRLF can set arbitrary cookies or split the response, leading to cache poisoning, HTTP response splitting, or cross-user cookie manipulation. With mTLS, the server might log the client certificate subject and include it in logs or headers; if those logs or headers are later reflected without sanitization, the CRLF payload can be stored and reused in log injection or header smuggling scenarios.
The combination of mTLS and Django’s header handling can also affect observability and filtering. MiddleBrick scans this attack surface by checking Authentication, Input Validation, and Data Exposure across unauthenticated endpoints. Even when mTLS is enforced at the edge, the application must treat all incoming headers as untrusted. For example, a view that reads request.META.get('HTTP_X_FORWARDED_HOST') and uses it in a redirect is vulnerable if the value contains CRLF characters. MiddleBrick’s checks for BFLA/Privilege Escalation and Property Authorization help surface these risky patterns by correlating authentication context with header usage.
Real-world mappings include OWASP API Top 10 2023:5 — Injection, and relevant CWE entries such as CWE-93 (CRLF Injection). In practice, the exploit chain involves an attacker supplying a malicious header, the Django app reflecting it into a redirect or Set-Cookie, and the client’s browser or downstream service interpreting the injected control characters. This can lead to session fixation, cache poisoning, or unauthorized redirects. Because mTLS verifies client identity, developers may overlook header validation, assuming the channel is trusted. However, validation must occur at the application layer regardless of transport security.
To detect this during a scan, tools can submit header values containing sequences like %0D%0ASet-Cookie: and inspect whether the response preserves the injection. MiddleBrick runs checks such as Input Validation and Data Exposure in parallel, testing how headers are handled and whether sensitive data or executable sequences leak. Even with mTLS, the scan verifies that responses do not reflect unsanitized input, ensuring that header manipulation does not compromise integrity.
Mutual Tls-Specific Remediation in Django — concrete code fixes
Remediation focuses on strict header validation and safe construction of responses in Django, regardless of mTLS presence. Always treat values from request.META, headers, and forwarded fields as untrusted. Use Django’s built-in utilities to sanitize and validate before including them in redirects, cookies, or custom headers.
Example 1: Safe redirect handling. Do not use user input directly in HttpResponseRedirect. Instead, validate against an allowlist of hostnames and use urllib.parse.urljoin to build absolute URLs safely.
from urllib.parse import urljoin, urlparse
from django.http import HttpResponseRedirect
from django.conf import settings
def safe_redirect(request):
next_url = request.GET.get('next', '/')
parsed = urlparse(next_url)
# Allowlist approach: restrict to known hosts or same host
allowed_hosts = getattr(settings, 'ALLOWED_REDIRECT_HOSTS', ['example.com'])
if parsed.hostname not in allowed_hosts:
# Fallback to default
next_url = '/'
# Ensure no CRLF in the final URL; urljoin normalizes but validate further if needed
safe_url = urljoin(request.build_absolute_uri(), next_url)
return HttpResponseRedirect(safe_url)
Example 2: Secure cookie setting with explicit parameters. Avoid reflecting headers into cookie values. If you must include client identity, encode and sign it rather than concatenating raw strings.
from django.http import HttpResponse
import json
def set_safe_cookie_view(request):
resp = HttpResponse('OK')
# Do not use raw headers; use verified certificate fields if needed
# For example, extract a subjectAltName claim via mTLS verification at the edge
client_id = 'verified-id-123' # obtained from mTLS verification, not from request
payload = json.dumps({'client_id': client_id})
resp.set_cookie('session', payload, httponly=True, samesite='Lax', secure=True)
return resp
Example 3: Custom header handling with strict newline filtering. If you propagate a header like X-Client-Cert, ensure it does not contain control characters. Use a sanitization function that strips or replaces CRLF sequences.
import re
from django.http import HttpResponse
SANITIZE_CRLF = re.compile(r'[\r\n]')
def sanitize_header_value(value: str) -> str:
return SANITIZE_CRLF.sub('', value)
def view_with_custom_header(request):
client_cert_subject = request.META.get('SSL_CLIENT_SUBJECT', '')
safe_subject = sanitize_header_value(client_cert_subject)
resp = HttpResponse('Processed')
resp['X-Client-Subject'] = safe_subject
return resp
Example 4: Middleware to reject headers containing CRLF. This provides a global safeguard for all incoming requests, complementing mTLS enforcement at the edge.
from django.utils.deprecation import MiddlewareMixin
import re
CRLF_RE = re.compile(r'[\r\n]')
class CrlfHeaderValidationMiddleware(MiddlewareMixin):
def process_request(self, request):
for key, value in request.headers.items():
if CRLF_RE.search(value):
raise SuspiciousOperation(f'CRLF detected in header {key}')
return None
These examples emphasize defense in depth: mTLS handles peer authentication at the transport layer, but the application must still validate and sanitize header-derived data. Combine these practices with runtime scans from MiddleBrick to continuously verify that headers are handled safely and that no injection vectors remain.