Cache Poisoning in Django with Hmac Signatures
Cache Poisoning in Django with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Cache poisoning occurs when an attacker causes a cache to store malicious content that is then served to other users. In Django, using HMAC signatures to validate data integrity can reduce some risks, but improper implementation can still lead to cache poisoning. HMAC (Hash-based Message Authentication Code) is typically used to verify that data has not been altered, yet if the cache key or cached content depends on unvalidated or attacker-influenced inputs, an attacker may manipulate the cache state.
Consider a scenario where Django caches personalized data (e.g., user preferences or API responses) keyed by a combination of user input and an HMAC. If the application includes attacker-controlled data in the cache key but only validates a subset of that data with the HMAC, an attacker can supply crafted input that results in a cache key that is valid (passes HMAC verification) but maps to a different logical cache entry. This can cause the cache to return the wrong data to other users or cause the application to recompute or re-fetch sensitive information, leading to information disclosure or degraded service.
Additionally, if the HMAC secret is weak or exposed, an attacker can generate valid signatures for malicious cache entries. In Django, when using HMAC signatures via django.core.signing (e.g., TimestampSigner or HMACSigner), the signature ensures integrity and authenticity of the signed value, but it does not automatically prevent the application from using untrusted input as part of the cache key. Caching layers such as Memcached or Redis do not understand HMAC semantics; they store and retrieve based on keys. If the key incorporates unvalidated input, the cache may conflate entries or allow key collisions that an attacker can exploit.
Real-world attack patterns mirror other injection and confusion weaknesses. For example, an attacker might probe endpoints that cache based on query parameters or headers and observe timing differences or content differences to infer whether a cache hit occurred. This behavior can resemble IDOR or BOLA when cached data is shared across users due to key collisions caused by manipulated input. The risk is compounded when the same HMAC-signed token is reused across requests or stored in client-side storage that an attacker can tamper with.
To understand the specific risk, it helps to map to real frameworks and standards. The OWASP API Top 10 category '2023-A5: Broken Function Level Authorization' and related BOLA/IDOR risks align with cache poisoning when authorization checks are bypassed via manipulated cache keys. Similarly, input validation failures (OWASP '2023-A1: Improper Inventory') can allow malformed or malicious inputs to affect cache behavior even when HMAC is used for signing. Proper mitigation requires validating and normalizing all inputs that influence cache keys and ensuring the HMAC covers the full canonical representation of the data used to generate the key.
Hmac Signatures-Specific Remediation in Django — concrete code fixes
Remediation focuses on ensuring that HMAC signatures cover all inputs that affect cache behavior and that cache keys are constructed from validated, canonical data. In Django, use django.core.signing to create signed values and validate them before using them in cache operations. Do not rely on signatures alone to protect cache keys; normalize and validate inputs before incorporating them into the key.
Example: Signed cache-safe payload
import json
from django.core.signing import TimestampSigner, BadSignature, SignatureExpired
signer = TimestampSigner()
def make_cached_payload(user_id, preference_data, max_age=3600):
# Canonicalize and serialize data
payload = json.dumps({'user_id': user_id, 'data': preference_data}, sort_keys=True, separators=(',', ':'))
signed = signer.sign(payload)
return signed
def get_cached_payload(signed_token, max_age=3600):
try:
payload = signer.unsign(signed_token, max_age=max_age)
return json.loads(payload)
except (BadSignature, SignatureExpired):
# Handle invalid or expired tokens securely
return None
Example: Cache key construction with validated inputs
import hashlib
def build_cache_key(user_id, validated_scope):
# Ensure scope is validated against an allowlist before use
canonical = f'preferences:{user_id}:{validated_scope}'
key = hashlib.sha256(canonical.encode('utf-8')).hexdigest()
return f'api_cache:{key}'
In these examples, the HMAC signing (via TimestampSigner) protects the integrity of the payload, while the cache key is derived from validated, normalized inputs. The key does not include raw user input; instead, it uses a deterministic hash of validated data. This prevents attackers from injecting key components that would cause cache collisions or serve incorrect data.
For broader protection, combine these practices with strict input validation and allowlists. Ensure any data included in cache-related logic is verified before use, and avoid concatenating raw strings that may include attacker-controlled values. These measures reduce the likelihood of cache poisoning even when HMAC signatures are involved.