HIGH cache poisoningflaskbasic auth

Cache Poisoning in Flask with Basic Auth

Cache Poisoning in Flask with Basic Auth — how this specific combination creates or exposes the vulnerability

Cache poisoning occurs when an attacker manipulates cached responses so that malicious or incorrect data is served to users. In Flask applications that use HTTP Basic Authentication, a common misconfiguration can cause authenticated responses to be cached and later served to unauthenticated or different authenticated users. This typically happens when caching is applied at the wrong layer (e.g., a CDN or reverse proxy) without including the Authorization header as part of the cache key, or when responses with sensitive data are marked as cacheable.

When Flask routes protected by Basic Auth do not explicitly prevent caching, intermediaries may store the response alongside the associated Authorization header or, more dangerously, strip the header and cache the body. If the cache key does not incorporate the credentials or the Authorization header, a cached response intended for one user might be delivered to another. For example, an authenticated request to /api/account could result in a cached response that includes sensitive account details; subsequent unauthenticated or low-privilege requests to the same URL may receive that cached data, leading to information disclosure.

Flask itself does not add strong cache-control headers by default, so developers must explicitly set headers such as Cache-Control: no-store for authenticated endpoints. Without these controls, any caching layer between the client and Flask may inadvertently store and reuse responses. The risk is compounded when responses contain personal data or tokens, as the cached content may persist beyond the user’s session. This pattern fits within the broader BOLA/IDOR checks performed by middleBrick, which detect endpoints that expose data across users.

An illustrative request flow shows the danger:

  • User A authenticates with Authorization: Basic dXNlcjE6cGFzc3dvcmQx and receives a JSON response cached by a shared proxy.
  • User B, who is not authenticated or has lower privileges, makes a request to the same URL without credentials.
  • The caching layer returns the cached response from User A, exposing User A’s data to User B.

middleBrick’s unauthenticated scan can identify endpoints that lack appropriate cache-control directives and that return sensitive data, flagging the potential for cache poisoning in combination with authentication mechanisms.

Basic Auth-Specific Remediation in Flask — concrete code fixes

To mitigate cache poisoning with Basic Auth in Flask, ensure authenticated responses are not cached by intermediaries and that cache keys account for the Authorization header. The following practices and code examples help secure Flask routes.

Set explicit cache-control headers for authenticated routes

For any route that validates credentials, set headers to prevent storage by shared caches. This ensures that responses are treated as private or non-cacheable.

from flask import Flask, request, make_response

app = Flask(__name__)

@app.route("/api/account")
def account():
    auth = request.authorization
    if not auth or not check_credentials(auth.username, auth.password):
        return {"error": "Unauthorized"}, 401
    response = make_response({"user": auth.username, "balance": 12345})
    response.headers["Cache-Control"] = "no-store, no-cache, must-revalidate, private"
    response.headers["Pragma"] = "no-cache"
    response.headers["Expires"] = "0"
    return response

def check_credentials(username, password):
    # Replace with secure credential verification
    return username == "alice" and password == "secret"

Vary responses by Authorization header at the caching layer

If you must use a caching layer, ensure that the cache key includes the Authorization header or a derived scope. Many CDNs and reverse proxies support a “Vary” header; configure them to vary by Authorization to avoid mixing user-specific responses.

# Example configuration concept for a caching proxy (not Flask code)
# Cache-Control: public, max-age=3600
# Vary: Authorization

In practice, avoid caching authenticated responses unless absolutely necessary, and scope caches per user or per token.

Use token-based auth with short lifetimes where feasible

While this is not a Flask code fix, replacing long-lived Basic Auth tokens with short-lived access tokens reduces the window of exposure if a cached response is served incorrectly. Combine this with strict cache-control headers for defense in depth.

Validate and test with automated security checks

Use tools like middleBrick to detect endpoints that may be vulnerable to cache poisoning. The scanner checks for missing cache-control directives on authenticated responses and highlights misconfigurations across the authentication and data exposure checks.

Frequently Asked Questions

Does setting Cache-Control headers alone fully prevent cache poisoning in Flask?
Setting Cache-Control headers such as no-store is necessary but may not be sufficient if upstream caches ignore headers or if the Vary directive is not properly configured to include the Authorization header. Always scope caches per user or avoid caching authenticated responses.
Can a CDN cache authenticated responses even when Flask sets no-store?
Yes, some CDNs may ignore no-store if misconfigured or if they terminate TLS and re-initiate to Flask with different headers. Ensure your CDN is configured to respect no-store and to vary cache keys by Authorization when authentication is used.