Dictionary Attack in Django with Mutual Tls
Dictionary Attack in Django with Mutual Tls — how this specific combination creates or exposes the vulnerability
A dictionary attack in Django when Mutual TLS is in play often shifts the attack surface from the application layer to the TLS and endpoint configuration. Django itself does not perform TLS termination; that typically happens at the reverse proxy or load balancer (e.g., Nginx, HAProxy, or a cloud load balancer). If Mutual TLS is required, the server requests and validates client certificates before forwarding requests to Django. A dictionary attack in this context usually targets the authentication endpoint protected by Mutual TLS, attempting many client certificate paths or usernames/passwords while appearing to come from valid client certificates.
The combination can expose risks if access control is applied at the wrong layer. For example, if the proxy enforces Mutual TLS and passes the client certificate information to Django via headers (e.g., SSL_CLIENT_S_DN_CN or HTTP_X_SSL_CLIENT_CERT), Django might trust those headers and implement a weaker authorization model. Attackers can then perform a dictionary attack on username/password or token-based endpoints while presenting valid client certificates, bypassing certificate-based boundary checks if the server does not also enforce per-user or per-client authorization inside Django.
Another vector involves unauthenticated LLM endpoints or insecure API consumption patterns. If a Django service exposes an endpoint that accepts arbitrary input and uses Mutual TLS only for client identification, an attacker could iterate through common client certificate subjects or use valid certificates paired with crafted payloads to probe for IDOR, BOLA, or privilege escalation. The 12 security checks in middleBrick run in parallel and would flag such endpoints under Authentication, BOLA/IDOR, and Unsafe Consumption, especially when LLM-style inputs are accepted without strict schema validation.
Consider an API endpoint that combines Mutual TLS with a username/password login. If the TLS layer validates the client certificate but Django does not enforce additional rate limiting or input validation, attackers can mount a dictionary attack against the login view while presenting a valid certificate. middleBrick’s checks for Rate Limiting, Input Validation, and Authentication would surface this as a high-severity finding, recommending per-user rate limits and strict schema validation even when Mutual TLS is enforced.
In OpenAPI/Swagger spec analysis, middleBrick resolves full $ref chains and cross-references runtime findings. If your spec describes Mutual TLS requirements but the implementation in Django does not enforce certificate-to-user mapping, the discrepancy is flagged. This helps teams see that Mutual TLS at the edge does not automatically translate to application-level authorization, a common misconfiguration that dictionary attacks can exploit.
Mutual Tls-Specific Remediation in Django — concrete code fixes
Remediation focuses on ensuring Django does not rely solely on Mutual TLS for authorization and that authentication logic remains explicit. You should validate client certificates at the proxy but also enforce user-level permissions and rate limits inside Django. Below are concrete patterns to implement in your Django project and proxy configuration.
- Configure your reverse proxy (e.g., Nginx) to require and verify client certificates, then map certificate fields to request metadata passed to Django. Do not rely on these headers for authorization without additional checks in Django.
# Example Nginx snippet for Mutual TLS
server {
listen 443 ssl;
ssl_certificate /etc/ssl/certs/server.crt;
ssl_certificate_key /etc/ssl/private/server.key;
ssl_client_certificate /etc/ssl/certs/ca.crt;
ssl_verify_client on;
location /api/ {
proxy_set_header X-SSL-Client-Verify $ssl_client_verify;
proxy_set_header X-SSL-Client-DN $ssl_client_s_dn;
proxy_set_header X-SSL-Client-CN $ssl_client_s_dn_com;
proxy_pass http://django_app;
}
}
- In Django, create a middleware that validates the presence of the proxy headers and maps them to a user, while also applying rate limiting and input validation. Do not trust the headers alone.
# Django middleware example
import hashlib
from django.http import HttpResponseForbidden
from django.core.cache import cache
class MutualTlsMappingMiddleware:
def __init__(self):
# In production, load a mapping from a secure store
self.cert_to_user = {
"CN=alice,O=Example": "alice",
"CN=bob,O=Example": "bob",
}
def __call__(self, request):
cert_cn = request.META.get("HTTP_X_SSL_CLIENT_CN")
cert_dn = request.META.get("HTTP_X_SSL_CLIENT_DN")
if not cert_cn or not cert_dn:
return HttpResponseForbidden("Client certificate required")
username = self.cert_to_user.get(cert_dn)
if not username:
return HttpResponseForbidden("Unmapped certificate")
request.user = type("User", (), {"username": username, "is_authenticated": True})()
# Apply rate limiting per user to mitigate dictionary attacks
key = f"rl:{username}:{request.path}"
count = cache.get(key, 0)
if count > 100:
return HttpResponseForbidden("Rate limit exceeded")
cache.set(key, count + 1, timeout=60)
return self.get_response(request)
- Use Django’s authentication backends to enforce username/password or token validation in addition to the proxy-level Mutual TLS. Combine with strong password policies and account lockout or throttling to make dictionary attacks ineffective.
# Example throttling in Django REST Framework
from rest_framework.throttling import UserRateThrottle
class DictionaryAttackThrottle(UserRateThrottle):
rate = '5/minute' # strict limit for login endpoints
- Validate and sanitize all inputs rigorously, even when Mutual TLS is used. Use Django forms or DRF serializers with strict schemas to prevent injection and over-posting that could be leveraged during dictionary-style probing.
# DRF serializer with strict validation
from rest_framework import serializers
class LoginSerializer(serializers.Serializer):
username = serializers.CharField(max_length=150)
password = serializers.CharField(min_length=12, write_only=True)
mfa_token = serializers.CharField(required=False)
def validate(self, data):
# Custom validation to ensure both cert and credentials align
if not data.get("password"):
raise serializers.ValidationError("Password is required")
return data
- For endpoints that also accept LLM-style or free-form input, apply strict schema validation and output scanning to prevent data exfiltration or prompt injection. middleBrick’s LLM/AI Security checks complement these measures by detecting system prompt leakage and testing for prompt injection, so consider integrating the CLI or dashboard to continuously monitor such risks.