Cache Poisoning in Phoenix with Hmac Signatures
Cache Poisoning in Phoenix 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 subsequently served to other users. In Phoenix applications, using Hmac Signatures to validate request integrity can inadvertently expose cache poisoning risks when the signature is included in the cache key or when the application caches responses that vary by signature parameters without proper normalization.
Phoenix typically uses Hmac Signatures to ensure that requests or generated tokens have not been tampered with. For example, a signed query parameter might be added to URLs to authorize access to cached resources. If the cache key incorporates the raw signature value, two different signatures for the same logical request will result in separate cache entries. This can lead to signature collisions or enable an attacker to inject a valid but maliciously crafted signature that maps to a poisoned cache entry.
Consider an endpoint that caches responses based on query parameters including a signature intended to prevent tampering. If the signature is derived from user-controlled input and the cache does not canonicalize inputs (e.g., omitting or normalizing the signature before caching), an attacker can craft a request with a valid Hmac Signature that maps to a manipulated resource representation. Because the signature validates integrity but does not prevent logical tampering of the input used to generate the cache key, the poisoned content may be served under a legitimate signature.
Real-world attack patterns mirror the OWASP API Top 10 category of Broken Object Level Authorization (BOLA) and Input Validation weaknesses. For instance, an attacker might manipulate URL parameters so that the Hmac Signature remains valid (e.g., by replaying a known good signature with altered parameters) and the cache serves tainted data. This is especially relevant when signatures are time-bound but the cache duration exceeds the intended validity window, allowing cross-user contamination.
An example of a vulnerable Phoenix controller action might look like this, where the signature is part of the parameters used for caching without sanitization:
def show(conn, %{"id" => id, "signature" => signature}) do
cache_key = {"resource", id, signature}
case Cachex.get(MyCache, cache_key) do
{:ok, nil} ->
data = fetch_resource(id)
verified = verify_hmac_signature(id, signature)
if verified do
Cachex.put(MyCache, cache_key, data)
json(conn, data)
else
send_resp(conn, 401, "Unauthorized")
end
{:ok, cached} ->
json(conn, cached)
end
end
In this pattern, the signature directly influences the cache key. If an attacker can obtain a valid signature for one set of parameters and reuse it with different parameters, the cache may serve incorrect data while the signature check passes. This demonstrates how Hmac Signatures in Phoenix can inadvertently facilitate cache poisoning when cache keys are not carefully designed to exclude or normalize signature-specific variability.
Hmac Signatures-Specific Remediation in Phoenix — concrete code fixes
To remediate cache poisoning when using Hmac Signatures in Phoenix, ensure that the cache key is independent of the signature value and that inputs are canonicalized before caching. The signature should be used strictly for request validation and not as part of the cache key.
First, validate the Hmac Signature separately from caching logic. Compute the cache key using only the business-relevant, non-signature parameters, and verify the signature after retrieving or before storing data. This prevents signature variability from affecting cache identity.
Here is a corrected Phoenix controller example that separates signature verification from caching:
def show(conn, %{"id" => id, "signature" => signature}) do
cache_key = {"resource", id}
case Cachex.get(MyCache, cache_key) do
{:ok, nil} ->
data = fetch_resource(id)
if verify_hmac_signature(id, signature) do
Cachex.put(MyCache, cache_key, data)
json(conn, data)
else
send_resp(conn, 401, "Unauthorized")
end
{:ok, cached} ->
json(conn, cached)
end
end
In this version, the cache key excludes the signature, so the same resource is cached once regardless of the signature used to access it. The signature is still validated on each request, ensuring integrity without polluting the cache layer.
Additionally, normalize all inputs that influence resource identification. For query parameters that affect the response but are not part of the cache key, ensure they are validated and canonicalized. For example, strip or standardize optional parameters before using them in business logic:
defp canonical_params(params) do
params
|> Map.drop(["signature"])
|> Map.drop(["token"])
|> Enum.sort()
|> Map.new()
end
Use this canonical map for cache keys and business logic, while keeping the raw signature for verification only. This approach aligns with secure coding practices for Hmac Signatures and mitigates cache poisoning by isolating signature validation from caching decisions.
For teams using the middleBrick CLI, you can scan your Phoenix endpoints to detect cache poisoning risks and receive remediation guidance as part of the security findings. The Pro plan supports continuous monitoring and CI/CD integration to catch regressions early, while the MCP Server allows you to scan APIs directly from your IDE during development.