HIGH rate limiting bypassdjangomutual tls

Rate Limiting Bypass in Django with Mutual Tls

Rate Limiting Bypass in Django with Mutual Tls — how this specific combination creates or exposes the vulnerability

Rate limiting in Django is typically implemented at the web server or application layer using identifiers such as IP address, API key, or user ID. When mutual TLS (mTLS) is introduced, the server also requests and validates a client certificate. Some developers assume that mTLS alone provides strong access control and may relax IP-based or session-based rate limiting, or they may apply rate limits per certificate rather than per end-user identity.

This combination can create a bypass when the client certificate is shared among multiple users or systems. A shared certificate means many distinct actors are seen as a single identifier, allowing one compromised client to consume a disproportionate share of requests. Additionally, if Django’s rate limiting relies on IP address and the mTLS handshake occurs at a proxy or load balancer, the origin IP may be lost unless explicitly forwarded. In that case, the server may fall back to certificate-based identification, which does not map cleanly to application users, enabling an authenticated client to exceed intended quotas.

Another scenario involves inconsistent enforcement across endpoints. Rate limiting applied to certain views but not others can allow an attacker to route activity through unthrottled paths, especially if certificate validation is trusted implicitly. The attacker might use a valid mTLS certificate to access administrative or high-cost endpoints that lack complementary rate limits, effectively bypassing protections designed for public-facing endpoints.

During a scan, middleBrick’s BFLA/Privilege Escalation and Rate Limiting checks simulate such bypass conditions by testing authenticated scenarios with shared or high-quota certificates and observing whether rate limits remain consistent across identities and paths. Findings include per-endpoint rate limit coverage, certificate-to-user mapping, and whether IP loss at the edge degrades protection.

Mutual Tls-Specific Remediation in Django — concrete code fixes

To securely combine rate limiting with mTLS in Django, tie limits to a stable, application-level identity rather than to the certificate or IP alone. Use the validated client certificate subject or a mapped user ID to enforce quotas consistently. Ensure the request pipeline preserves the original IP when behind proxies by configuring trusted headers.

Example Django settings for mTLS with proper client validation

import ssl
from pathlib import Path

# settings.py

BASE_DIR = Path(__file__).resolve().parent.parent

# Enable secure SSL settings for mTLS
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_SECURE = True

# Assume certificates are stored in a secure directory
SSL_CERTIFICATE_PATH = BASE_DIR / 'certs' / 'ca.pem'
SSL_CLIENT_CERT_PATH = BASE_DIR / 'certs' / 'client.pem'
SSL_CLIENT_KEY_PATH = BASE_DIR / 'certs' / 'client-key.pem'

# Custom middleware to extract and validate client certificate details
class MutualTlsMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        # The proxy or server should have already validated the client cert
        # and set a header like X-Client-Subject with the certificate's subject DN
        client_subject = request.META.get('HTTP_X_CLIENT_SUBJECT')
        request.client_subject = client_subject  # stable identifier for rate limiting
        response = self.get_response(request)
        return response

# Add MIDDLEWARE in settings.py
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'yourapp.middleware.MutualTlsMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

# Configure secure SSL context for upstream mTLS verification if terminating at Django
# Note: In production, mTLS is often handled at a reverse proxy or load balancer.
# The example below is for reference when Django terminates TLS.
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True

# Use a robust data store for rate limiting, e.g., Redis
RATELIMIT_BACKEND = 'django_ratelimit.backends.cache.RateLimitCacheBackend'
CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache',
        'LOCATION': 'redis://127.0.0.1:6379/1',
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient',
        }
    }
}

Applying rate limits using a certificate-mapped identifier

from ratelimit.decorators import ratelimit
from django.http import JsonResponse

@ratelimit(key='client_subject', rate='100/m', block=True)
def sensitive_view(request):
    # key is derived from the middleware-set request.client_subject
    return JsonResponse({'status': 'ok', 'subject': request.client_subject})

# Alternatively, use a custom rate limit decorator that combines identity and path
def rate_limit_by_identity_and_path(view_func):
    def wrapped(request, *args, **kwargs):
        identity = getattr(request, 'client_subject', request.META.get('REMOTE_ADDR'))
        # Implement custom logic using Django cache or a token bucket
        # For brevity, using ratelimit library with composite key
        composite_key = f'{identity}:{request.path}'
        # Assume a utility check_rate_limit(composite_key) exists
        if not check_rate_limit(composite_key):
            return JsonResponse({'error': 'rate limit exceeded'}, status=429)
        return view_func(request, *args, **kwargs)
    return wrapped

In production, terminate mTLS at a proxy or API gateway and ensure the gateway sets a reliable header (e.g., X-Client-Subject) that maps to a user or service identity. Configure Django to trust the proxy and use that header for rate limiting. middleBrick’s scans verify that rate limits are applied consistently across endpoints and that certificate-based identifiers map correctly to application users, reducing the risk of quota abuse via shared or missing mappings.

Related CWEs: resourceConsumption

CWE IDNameSeverity
CWE-400Uncontrolled Resource Consumption HIGH
CWE-770Allocation of Resources Without Limits MEDIUM
CWE-799Improper Control of Interaction Frequency MEDIUM
CWE-835Infinite Loop HIGH
CWE-1050Excessive Platform Resource Consumption MEDIUM

Frequently Asked Questions

Can a shared client certificate bypass rate limits in Django?
Yes. If multiple users or services share a single client certificate and rate limiting is tied to the certificate rather than to individual identities, one actor can exhaust quotas intended for many. Use certificate-to-user mapping and apply limits to stable user identifiers.
What happens if the original IP is lost in an mTLS setup behind a proxy?
Django may misattribute rate limits if it falls back to certificate or host-based identifiers. Ensure the proxy sets headers like X-Forwarded-For and configure Django’s SECURE_PROXY_SSL_HEADER and middleware to preserve and trust the original IP for accurate rate limiting.