Cors Wildcard in Django with Mutual Tls
Cors Wildcard in Django with Mutual Tls — how this specific combination creates or exposes the vulnerability
A CORS wildcard (Access-Control-Allow-Origin: *) combined with Mutual TLS (mTLS) in Django can weaken origin-based isolation. With mTLS, the server validates the client certificate, but CORS is a browser-enforced mechanism. If you configure Django to respond with a wildcard Access-Control-Allow-Origin while also requiring client certificates, a browser may still allow a malicious page from any origin to make authenticated requests, because the browser completes the TLS handshake (presenting a valid client cert) and then applies CORS rules. If the wildcard is too permissive, the browser will accept the response, potentially exposing authenticated sessions or sensitive endpoints to a malicious site that can supply a valid client certificate (e.g., via a compromised device or a user who installed a rogue CA).
Consider an API endpoint that returns sensitive data and uses mTLS for client authentication. If CORS_ALLOW_ALL_ORIGINS = True (or Access-Control-Allow-Origin: *) and the view relies on mTLS for authorization, any origin that can provide a valid client certificate (e.g., through a user-installed certificate) can read the response. This blurs the boundary between transport-layer authentication and application-layer authorization. While mTLS ensures the client is trusted, CORS should still restrict which origins are permitted in the browser. A wildcard in this context allows any site to initiate requests that the browser will accept, making it easier for an attacker to chain mTLS client credentials with a malicious frontend to perform actions on behalf of the user, such as reading PII or triggering state changes.
Moreover, if preflight requests (OPTIONS) respond with a wildcard and allow any headers or methods, an attacker can probe which HTTP verbs and headers the mTLS-protected endpoint supports, aiding in crafting further attacks. In Django, common misconfiguration is using django-cors-headers with CORS_ALLOW_ALL_ORIGINS = True while also enforcing mTLS at the load balancer or application layer. This setup doesn’t break mTLS, but it weakens the effectiveness of origin-based security in the browser, because the CORS policy is more permissive than intended.
Mutual Tls-Specific Remediation in Django — concrete code fixes
Remediate by tightening CORS to specific origins and ensuring mTLS validation is correctly integrated. Avoid wildcards when mTLS is used for authentication-like decisions. Below are concrete Django settings and view examples.
- Install and configure
django-cors-headerswith specific origins:
# settings.py
CORS_ALLOWED_ORIGINS = [
"https://trusted.example.com",
"https://app.example.com",
]
# Only allow specific methods and headers for preflight
CORS_ALLOW_METHODS = [
"GET",
"POST",
"PUT",
"DELETE",
]
CORS_ALLOW_HEADERS = [
"authorization",
"content-type",
"x-requested-with",
]
# Ensure cookies are not sent cross-origin unless necessary
CORS_ALLOW_CREDENTIALS = True # only if you need to send cookies
- Enforce mTLS at the server/reverse proxy and map the client certificate to a user in Django. For example, using a middleware that reads the verified client certificate from the request:
# middleware.py
import ssl
from django.http import HttpResponseForbidden
from django.conf import settings
def get_cert_subject_dn(cert):
# Example: extract subject DN from the PEM certificate
import ssl
from cryptography import x509
from cryptography.hazmat.backends import default_backend
cert_obj = x509.load_pem_x509_certificate(cert.encode('utf-8'), default_backend())
subject = cert_obj.subject.rfc4514_string()
return subject
class MutualTlsAuthMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
cert = request.META.get('SSL_CLIENT_CERT')
if not cert:
return HttpResponseForbidden('Client certificate required')
subject = get_cert_subject_dn(cert)
# Map subject to a Django user (example logic)
user = self.map_subject_to_user(subject)
if user is None:
return HttpResponseForbidden('Certificate not authorized')
request.user = user
return self.get_response(request)
def map_subject_to_user(self, subject):
# Implement your mapping, e.g., look up by CN or OU
from django.contrib.auth.models import User
# Example: subject like 'CN=alice,O=Example'
if 'CN=alice' in subject:
return User.objects.get(username='alice')
return None
- Ensure the middleware is placed after authentication middleware and HTTPS is enforced:
# settings.py
MIDDLEWARE = [
...
'django.contrib.auth.middleware.AuthenticationMiddleware',
'yourapp.middleware.MutualTlsAuthMiddleware',
'corsheaders.middleware.CorsMiddleware', # should be near top but after security middlewares
...
]
# Require HTTPS in production
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
- In your views, rely on
request.userset by the middleware instead of re-checking the certificate. Combine with Django’s permission system:
# views.py
from django.http import JsonResponse
from django.contrib.auth.decorators import login_required
@login_required
def sensitive_data(request):
# request.user is already validated via mTLS middleware
data = {"message": "confidential", "user": request.user.username}
return JsonResponse(data)
By aligning CORS origins with your trusted frontends and enforcing mTLS via middleware, you maintain defense-in-depth: mTLS provides strong client authentication, and CORS ensures only permitted origins can access the browser context.
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 |