HIGH cross site request forgerydjangomutual tls

Cross Site Request Forgery in Django with Mutual Tls

Cross Site Request Forgery in Django with Mutual Tls

Cross Site Request Forgery (CSRF) is an attack that forces an authenticated user to execute unwanted actions on a web application in which they are currently authenticated. In Django, CSRF protection is enabled by default for views rendered via templates and for form submissions, relying on synchronized tokens and the SameSite attribute on cookies. When Mutual Transport Layer Security (Mutual TLS) is used, the server and client authenticate each other using digital certificates. While Mutual TLS provides strong transport-layer identity and can prevent some network-level attacks, it does not inherently protect against CSRF at the application layer.

Mutual TLS binds client identity to the TLS session, which can complement application-level defenses but does not replace CSRF tokens. A common misconception is that Mutual TLS alone prevents CSRF; however, CSRF exploits the browser’s automatic inclusion of cookies for the target origin. If a client certificate is used to authenticate the user and the session relies on cookie-based session identifiers, an attacker can still craft a request from a different origin that the browser sends automatically, provided the browser has a valid session cookie. Mutual TLS does not block this browser behavior, so the application remains vulnerable unless explicit CSRF mitigations are in place.

Another subtle interaction involves API clients that use Mutual TLS for authentication. For example, a JavaScript frontend calling a Django API over HTTPS with a client certificate might still include CSRF-vulnerable patterns if it relies on cookies for session management. If the API endpoints accept state-changing methods like POST, PUT, or DELETE without requiring a CSRF token or a custom authorization header, an attacker can trick an authenticated client into issuing unintended requests. Therefore, when using Mutual TLS in Django, you must ensure that CSRF protections are applied to relevant views and that cookie usage is reviewed to avoid mixing authentication and session cookies without proper CSRF safeguards.

Mutual Tls-Specific Remediation in Django

To secure Django applications using Mutual TLS while preventing CSRF, apply defense-in-depth: rely on Django’s built-in CSRF middleware and combine it with strict transport and client identity validation. Below are concrete code examples demonstrating how to configure Mutual TLS and enforce CSRF protection.

Django settings for Mutual TLS and CSRF

# settings.py
import os

# Enforce HTTPS in production
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True

# Ensure CSRF checks are enabled (default is True for most middleware)
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
]

# Optional: set SameSite policy for CSRF and session cookies
CSRF_COOKIE_SAMESITE = 'Strict'
SESSION_COOKIE_SAMESITE = 'Strict'

Mutual TLS configuration with a reverse proxy

Mutual TLS is typically enforced at the load balancer or reverse proxy (e.g., Nginx). Django receives the authenticated client identity via a trusted header. Validate this header strictly and avoid using it for CSRF decisions unless you also verify the TLS session.

# settings.py
# Trust the proxy header set by the Mutual TLS termination point
CSRF_TRUSTED_ORIGINS = ['https://api.example.com']
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

# Example header set by the proxy after successful client cert validation
CLIENT_CERT_HEADER = 'SSL_CLIENT_CERT_SUBJECT'

View-level CSRF exemption with Mutual TLS (use cautiously)

If you have a dedicated Mutual TLS-authenticated endpoint that does not rely on cookies, you may selectively exempt CSRF. Ensure the view verifies the client certificate and does not accept unsafe methods from untrusted origins.

# views.py
from django.views.decorators.csrf import csrf_exempt
from django.http import JsonResponse

def my_mtls_endpoint(request):
    # Example: inspect the proxy header for client certificate details
    cert_subject = request.META.get('HTTP_SSL_CLIENT_CERT_SUBJECT')
    if not cert_subject:
        return JsonResponse({'error': 'Client certificate required'}, status=403)
    # Perform additional authorization checks here
    return JsonResponse({'status': 'ok'})

# Explicitly exempt only if you have other strong transport identity guarantees
my_mtls_endpoint.csrf_exempt = True

Using custom headers to augment CSRF protection

For APIs, require a custom header (e.g., X-API-Key or X-CSRFToken) alongside CSRF tokens for state-changing requests. This reduces reliance on cookie-based CSRF in hybrid web/Mutual TLS flows.

# views.py
from django.views.decorators.csrf import csrf_exempt
from django.http import JsonResponse

def csrf_protected_post(request):
    if request.method != 'POST':
        return JsonResponse({'error': 'Method not allowed'}, status=405)
    api_key = request.META.get('HTTP_X_API_KEY')
    if api_key != os.getenv('EXPECTED_API_KEY'):
        return JsonResponse({'error': 'Invalid API key'}, status=403)
    # Proceed with CSRF check for cookie-based session if applicable
    # (Django middleware will handle CSRF validation)
    return JsonResponse({'status': 'success'})

Testing CSRF with Mutual TLS locally

Use curl to verify that requests without proper cookies or headers are rejected, even when a client certificate is presented. This ensures your CSRF middleware and Mutual TLS configuration work together.

# Example curl command with client certificate
curl --cert client.crt --key client.key \
  --cacert ca.pem \
  -H "X-Requested-With: XMLHttpRequest" \
  -X POST https://localhost:8000/api/action \
  -d "csrfmiddlewaretoken=$(python -c 'import secrets; print(secrets.token_hex(16))')"

Frequently Asked Questions

Does Mutual TLS replace CSRF tokens in Django?
No. Mutual TLS provides strong transport-layer client authentication but does not prevent browsers from automatically sending cookies that can be exploited in CSRF attacks. You should still use Django’s CSRF middleware and tokens for state-changing requests.
How should Django handle CSRF when using Mutual TLS with APIs?
For Mutual TLS-authenticated API endpoints, require a custom header (e.g., X-API-Key) and validate client certificates at the proxy. Apply Django’s CSRF middleware only where cookie-based sessions are used; consider csrf_exempt only when you have other strong identity guarantees and validate all inputs rigorously.