Cache Poisoning in Django with Basic Auth
Cache Poisoning in Django with Basic Auth — how this specific combination creates or exposes the vulnerability
Cache poisoning occurs when an attacker causes a cache to store malicious or incorrect responses that are then served to other users. In Django, combining HTTP Basic Authentication with a cache layer can unintentionally expose sensitive data or allow an attacker to poison cached responses when authentication information is mishandled.
When a view that uses Basic Auth is cached without considering the Authorization header, the same cached response can be reused across different users. For example, if a cache key is derived only from the request path and query string, a response authenticated for one user might be served to another user who should not have access. This effectively bypasses Basic Auth protections at the caching layer.
Consider a Django view that returns user-specific data and is protected by Basic Auth but is cached with a naive key like request.path. An authenticated request from Alice could be cached, and a subsequent request from Bob to the same URL might receive Alice’s cached response, exposing private information. Additionally, if query parameters include user identifiers or tokens without excluding the Authorization header from the cache key, an attacker may inject crafted query values to poison the cache with malicious content.
Django’s cache framework does not automatically exclude sensitive headers when forming cache keys. If the caching middleware or backend uses the full request URI without omitting the Authorization header, the cache treats authenticated and unauthenticated requests as identical. This misconfiguration can lead to privilege escalation or data leakage, which aligns with BOLA/IDOR findings in automated scans like those performed by middleBrick.
Another vector involves caching responses that vary by credentials but failing to include the normalized credentials in the cache key. If an endpoint returns different representations based on Basic Auth credentials yet the cache ignores those headers, an attacker may coerce the server into caching a public response under a private key or vice versa. This can be exacerbated if the view uses HTTP caching headers such as Cache-Control: public while still requiring authentication, creating a mismatch between intended audience and cached content.
To detect these issues, security scanners perform unauthenticated checks to identify whether cache-related headers are respected across authenticated endpoints. Tools like middleBrick run parallel checks including Authentication, BOLA/IDOR, and Property Authorization to surface misconfigurations where authentication and caching intersect. These findings help teams understand exposure without implying automatic remediation, as scans report rather than fix.
Basic Auth-Specific Remediation in Django — concrete code fixes
To securely use HTTP Basic Auth with caching in Django, ensure cache keys incorporate the authentication state or exclude authenticated responses from shared caches. Below are concrete, working examples.
Example 1: Vary on Authorization header
Use Django’s Vary header to indicate that the response varies based on the Authorization header. This instructs caches and browsers to store separate copies per authentication state.
from django.views.decorators.vary import vary_on_headers
from django.http import JsonResponse
@vary_on_headers('Authorization')
def my_protected_view(request):
auth_header = request.META.get('HTTP_AUTHORIZATION', '')
if not auth_header.startswith('Basic '):
return JsonResponse({'error': 'Unauthorized'}, status=401)
# Perform Basic Auth validation here
return JsonResponse({'data': 'safe response'})
Example 2: Custom cache key excluding credentials
When using a low-level cache, construct a cache key that omits the raw Authorization header and instead uses a normalized identifier or a hash of the credentials if necessary. Avoid storing sensitive values in the key itself, but do differentiate per-user caches where appropriate.
import hashlib
from django.core.cache import cache
from django.http import JsonResponse
import base64
def get_cache_key(request):
path = request.path
# Optionally include a hash of the credentials if user-specific caching is required
auth = request.META.get('HTTP_AUTHORIZATION', '')
if auth.startswith('Basic '):
try:
encoded = auth.split(' ')[1]
decoded = base64.b64decode(encoded).decode('utf-8')
username, _ = decoded.split(':', 1)
user_suffix = hashlib.sha256(username.encode()).hexdigest()[:8]
except Exception:
user_suffix = 'anon'
else:
user_suffix = 'anon'
return f'view:{path}:{user_suffix}'
def my_view(request):
key = get_cache_key(request)
cached = cache.get(key)
if cached is not None:
return cached
# Generate response
response = JsonResponse({'user': 'derived-from-key'})
cache.set(key, response, timeout=60)
return response
Example 3: Disable caching for authenticated endpoints
If caching authenticated responses is not required, explicitly disable caching for views that use Basic Auth by setting cache-control headers.
from django.views.decorators.cache import never_cache
from django.http import JsonResponse
@never_cache
def my_authenticated_view(request):
auth = request.META.get('HTTP_AUTHORIZATION', '')
if not auth.startswith('Basic '):
return JsonResponse({'error': 'Unauthorized'}, status=401)
# Validate credentials
return JsonResponse({'data': 'no-cache response'})
Example 4: Using middleware to strip sensitive headers before caching
Implement a lightweight middleware to remove or normalize the Authorization header before the cache key is derived, ensuring that public cache entries are not keyed on credentials.
class CacheSafeAuthMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Remove Authorization header for cache key generation if not needed
# This does not strip the header from the request for authentication,
# it only influences cache key logic when combined with custom cache backends.
response = self.get_response(request)
return response
Operational guidance
- Always set
Vary: Authorizationwhen the response meaningfully changes with credentials. - Avoid using public cache directives on endpoints that require authentication unless responses are intentionally shared.
- Audit cache keys to ensure they do not inadvertently correlate private user data across users.
These code examples demonstrate how to align Django’s caching behavior with Basic Auth requirements. Security scanners may still flag edge cases; prioritize remediation guidance from findings that map to frameworks like OWASP API Top 10.