Phishing Api Keys in Django with Mutual Tls
Phishing API Keys in Django with Mutual TLS — how this specific combination creates or exposes the vulnerability
Mutual Transport Layer Security (mTLS) in Django means both the client and the server present certificates during the TLS handshake. When mTLS is used, developers may assume the channel is fully authenticated and therefore treat every request as trusted. This assumption can lead to unsafe handling of API keys that are still passed in application-level headers, cookies, or query parameters, because an attacker can still phish those keys from the client or from logs.
In Django, API keys are often stored in settings, environment variables, or injected into request headers by a service mesh or client. If a client application embeds an API key in a header like X-API-Key, and the Django backend uses mTLS only for transport authentication, the key is still present in plaintext in HTTP requests. Phishing can occur through compromised client machines, malicious browser extensions, or insecure logging that captures these headers. Even with mTLS protecting the channel, a captured X-API-Key can be reused to impersonate the service because mTLS does not validate the semantic content of the key itself.
Another scenario involves OAuth or session-based flows where mTLS is used but the API key is returned in a redirect URL or stored in a cookie without the Secure and HttpOnly flags. An attacker who can manipulate the client environment can exfiltrate the key. Additionally, if Django’s debug toolbar or error pages inadvertently expose request headers, an mTLS-enabled endpoint might still leak API keys in plaintext within logs or error traces.
The combination of mTLS and Django does not inherently prevent key leakage at the application layer. mTLS authenticates endpoints, but API keys require additional safeguards: avoid passing keys in URLs, do not log headers containing keys, enforce strict referrer policies, and rotate keys regularly. Tools like middleBrick can scan your Django API endpoints to detect whether API keys are exposed in responses or logs, even when mTLS is in place, by checking for insecure handling patterns and missing protections such as input validation and data exposure controls.
Mutual TLS-Specific Remediation in Django — concrete code fixes
To securely use mTLS in Django, treat client certificates as an additional authentication factor and do not rely on them to protect API keys. Enforce strict header handling, validate client certificates at the application level when necessary, and ensure API keys are never exposed in logs or error responses.
Example mTLS-enabled Django settings
import os
# settings.py
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
CSRF_COOKIE_HTTPONLY = True
# Require client certificates (handled by the web server, e.g., Nginx or Apache)
# Django receives the validated client identity via a header set by the proxy,
# typically mapped to REMOTE_USER or a custom header like SSL_CLIENT_CERT.
Nginx mTLS configuration example
server {
listen 443 ssl;
server_name api.example.com;
ssl_certificate /etc/ssl/certs/server.crt;
ssl_certificate_key /etc/ssl/private/server.key;
# Enable mTLS: client must present a valid certificate
ssl_verify_client on;
ssl_client_certificate /etc/ssl/certs/ca-chain.pem;
# Map the client certificate fields to environment variables or headers
# Common fields: SSL_CLIENT_S_DN, SSL_CLIENT_VERIFY
proxy_set_header X-SSL-Client-Subject $ssl_client_s_dn;
proxy_set_header X-SSL-Client-Verify $ssl_client_verify;
location / {
proxy_pass http://django_app;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Django view handling client certificate information safely
import logging
from django.http import JsonResponse
from django.views.decorators.http import require_GET
logger = logging.getLogger(__name__)
@require_GET
def secure_endpoint(request):
# Use the header set by the proxy (e.g., X-SSL-Client-Subject)
client_subject = request.META.get('HTTP_X_SSL_CLIENT_SUBJECT')
client_verify = request.META.get('HTTP_X_SSL_CLIENT_VERIFY')
if client_verify != 'SUCCESS':
return JsonResponse({'error': 'Client certificate not verified'}, status=403)
# Do not log raw certificate details that may contain sensitive info
logger.info('mTLS authenticated client: %s', client_subject)
# Ensure API keys are not passed in query params or exposed in response
api_key = os.environ.get('INTERNAL_API_KEY')
if not api_key:
return JsonResponse({'error': 'Internal configuration error'}, status=500)
# Use the key internally without echoing it back to the client
# Perform business logic here
return JsonResponse({'status': 'ok', 'client': client_subject})
Protecting API keys in requests and logs
- Never include API keys in URL query strings or fragments; use headers only when necessary and ensure they are not logged.
- Configure Django logging to filter out sensitive headers. For example, create a custom logging filter to redact
X-API-Keyfrom log records. - Set
SECURE_SSL_REDIRECT = Trueto enforce HTTPS and prevent downgrade attacks that could expose keys. - Use short-lived API keys and rotate them regularly; combine this with mTLS to reduce the impact of a leaked key.
- Validate and sanitize all inputs rigorously to prevent injection attacks that could expose stored keys.
middleBrick can complement these measures by scanning your Django API endpoints for exposed API keys and insecure header handling, providing findings with severity levels and remediation guidance even when mTLS is deployed.