HIGH cache poisoningdjangobasic auth

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: Authorization when 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.

Frequently Asked Questions

Does middleBrick automatically fix cache poisoning issues in Django with Basic Auth?
No. middleBrick detects and reports findings with remediation guidance, but it does not automatically fix or patch issues. You must apply recommended code changes such as varying on the Authorization header or adjusting cache keys.
Can scanning authenticated endpoints with Basic Auth expose credentials during a scan?
middleBrick tests the unauthenticated attack surface by default. If you want authenticated scans, provide credentials separately; the tool does not store or misuse credentials and returns findings without exposing passwords.