Ssrf in Django with Mutual Tls
Ssrf in Django with Mutual Tls — how this specific combination creates or exposes the vulnerability
Server-Side Request Forgery (SSRF) in Django becomes particularly nuanced when Mutual TLS (mTLS) is in use. mTLS requires both the client and the server to present valid certificates during the TLS handshake. In Django, developers often use requests or httpx to call downstream services and may configure mTLS by providing cafile, certfile, and keyfile paths. When an attacker can influence the target URL of an HTTP request made by the server, they can direct the server to connect to internal endpoints that are not intended to be exposed. Even with mTLS protecting inbound traffic to your Django app, an SSRF against an outbound mTLS-enabled service can bypass network-level segregation because the Django process itself holds a valid client certificate and private key.
Consider a scenario where a Django view accepts a URL parameter to fetch metadata from a partner service that enforces mTLS:
import requests
from django.http import JsonResponse
from django.views.decorators.http import require_GET
@require_GET
def fetch_service_metadata(request):
url = request.GET.get('url')
# WARNING: user-controlled URL with mTLS client certs
resp = requests.get(
url,
cert=('client.crt', 'client.key'),
verify='ca-bundle.crt'
)
return JsonResponse({'status': resp.status_code, 'text': resp.text[:200]})
If the attacker provides a URL pointing to an internal mTLS-protected admin endpoint (e.g., https://internal-service:8443/admin/debug), the Django server will successfully authenticate using its client certificate and relay the response, effectively pivoting through mTLS authentication. The presence of mTLS may create a false sense of security: it secures server-to-server communication but does not prevent the Django app from being used as a proxy to internal mTLS-protected systems. Additional risks emerge when the downstream service trusts the client certificate presented by Django, allowing access to restricted resources. Moreover, certificate validation misconfigurations (e.g., verify=False or improper ca bundles) can compound the issue, enabling man-in-the-middle scenarios or bypassing intended trust boundaries.
middleBrick identifies such patterns by correling OpenAPI specifications with runtime behavior, including mTLS configurations where client certificates are referenced. This helps highlight how an SSRF vector can leverage trusted identities in mTLS workflows, emphasizing the need to treat outbound requests with the same scrutiny as inbound protections.
Mutual Tls-Specific Remediation in Django — concrete code fixes
Remediation centers on input validation, network segregation, and minimizing the trust surface of client certificates. Avoid forwarding user-supplied URLs directly to services that use mTLS. If you must allow dynamic endpoints, enforce a strict allowlist of hostnames and paths, and use a dedicated outbound mTLS context that does not expose elevated privileges.
1) Validate and restrict target hosts
Instead of passing user input to requests, map allowed services to a whitelist and construct the request safely:
from urllib.parse import urlparse import requests from django.http import JsonResponse from django.views.decorators.http import require_GET ALLOWED_HOSTS = {'api.partner.com', 'data.example.org'} @require_GET def fetch_service_metadata_safe(request): url = request.GET.get('url') parsed = urlparse(url) if parsed.hostname not in ALLOWED_HOSTS: return JsonResponse({'error': 'host not allowed'}, status=400) # Use a fixed client certificate scoped for outbound calls only resp = requests.get( url, cert=('outbound_client.crt', 'outbound_client.key'), verify='ca-bundle-partner.crt' ) return JsonResponse({'status': resp.status_code, 'text': resp.text[:200]})2) Use a dedicated outbound TLS context and avoid certificate propagation
Do not reuse the same client certificate used for inbound mTLS. Create a separate certificate with minimal privileges for outbound calls:
import requests from django.http import JsonResponse from django.views.decorators.http import require_GET @require_GET def fetch_with_isolated_mtls(request): url = request.GET.get('url') parsed = urlparse(url) if parsed.hostname not in {'api.partner.com'}: return JsonResponse({'error': 'host not allowed'}, status=400) # Dedicated certificate scoped for this specific partner resp = requests.get( url, cert=('outbound_partner.crt', 'outbound_partner.key'), verify='/etc/ssl/certs/partner-ca.crt' ) return JsonResponse({'status': resp.status_code})3) Disable client certificate forwarding to untrusted services
If you must call multiple services, instantiate separate sessions with specific certificates instead of a single client context that could be inadvertently used everywhere:
import requests def get_partner_session(): session = requests.Session() session.cert = ('partner_only.crt', 'partner_only.key') session.verify = '/etc/ssl/certs/partner-ca.crt' return session session = get_partner_session() resp = session.get('https://api.partner.com/metadata')4) Enforce network-level controls and certificate pinning
Use Kubernetes NetworkPolicy or firewall rules to restrict egress to known endpoints. For higher assurance, pin the server certificate or public key hash in your verification logic to reduce reliance on CA trust alone:
import requests import ssl from django.http import JsonResponse from django.views.decorators.http import require_GET @require_GET def fetch_with_pinning(request): url = request.GET.get('url') parsed = urlparse(url) if parsed.hostname != 'api.partner.com': return JsonResponse({'error': 'host not allowed'}, status=400) # Pin a specific public key hash (SPKI pin) session = requests.Session() session.cert = ('outbound.crt', 'outbound.key') session.verify = '/etc/ssl/certs/ca.pem' # Custom verification can enforce pin checks here resp = session.get(url) return JsonResponse({'status': resp.status_code})These practices ensure that mTLS strengthens outbound calls without turning the Django app into a conduit to internal mTLS-protected assets. Security checks like those performed by middleBrick can validate that client certificates are not over-privileged and that the runtime behavior aligns with intended network boundaries.
Related CWEs: ssrf
| CWE ID | Name | Severity |
|---|---|---|
| CWE-918 | Server-Side Request Forgery (SSRF) | CRITICAL |
| CWE-441 | Unintended Proxy or Intermediary (Confused Deputy) | HIGH |