Cache Poisoning in Hanami with Hmac Signatures
Cache Poisoning in Hanami 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, which is then served to other users. In Hanami, using Hmac Signatures for request authentication can inadvertently expose cache poisoning risks if the signature does not cover all inputs that influence the cache key. When a cache layer (e.g., a CDN or reverse proxy) uses only the request path or a subset of headers/query parameters to form the cache key, and the Hmac Signature is applied only to the request body or a subset of parameters, an attacker can manipulate unsigned parameters to poison the cache while the signature remains valid.
For example, consider an endpoint that personalizes content based on an unsigned query parameter such as user_id, while the Hmac Signature is computed over the request path and the request body. An authenticated attacker can craft requests with different user_id values that map to the same Hmac Signature (if the signature does not include that parameter), causing the cache to store a response intended for one user and serve it to another. This is a form of BOLA/IDOR via cache poisoning because the cached response leaks one user’s data to another. The risk is compounded when the application uses shared caches and the signature does not protect the full request context used by the cache key.
In Hanami, this often maps to the BOLA/IDOR and Data Exposure checks run by middleBrick. The scanner tests whether cache-sensitive parameters are included in the signature or otherwise protected. If the Hmac Signature is computed over a non-unique or non-authoritative set of inputs, the scanner may flag findings related to insufficient input validation and improper authorization, noting that the signature does not prevent cache manipulation.
Real-world attack patterns include an attacker observing a valid request with a signed query or header, then altering an unsigned parameter (such as a locale or currency field) to cause the cache to return a different representation. Because the Hmac Signature remains valid, the server processes the request as trusted, and the poisoned cache entry serves malicious or sensitive content to other users. This aligns with OWASP API Top 10 #1 (Broken Object Level Authorization) and can lead to data exposure as defined in PCI-DSS and SOC2 controls.
middleBrick’s LLM/AI Security checks do not directly test cache poisoning, but the scanner’s BOLA and Data Exposure tests help identify whether signatures cover the right inputs. The scan results include prioritized findings with severity and remediation guidance, helping teams understand how to align Hmac Signatures with cache key design.
Hmac Signatures-Specific Remediation in Hanami — concrete code fixes
To remediate cache poisoning when using Hmac Signatures in Hanami, ensure the signature covers all inputs that influence the cache key and the response. This typically means including query parameters, selected headers, and the request path in the string-to-sign. Below are concrete code examples for a Hanami application that uses Hmac Signatures correctly to prevent cache poisoning.
Example 1: Hmac Signature over path and selected query parameters
In this example, the signature is computed over the request path and specific query parameters that affect caching and authorization. The server verifies the signature before using any query parameters to construct the cache key.
require "openssl"
require "uri"
module Security
class HmacValidator
def self.generate_signature(secret, path, params)
data = { path: path, params: params }.to_json
OpenSSL::HMAC.hexdigest("sha256", secret, data)
end
def self.verify_signature(secret, path, params, provided_signature)
expected = generate_signature(secret, path, params)
secure_compare(expected, provided_signature)
end
private
def self.secure_compare(a, b)
return false unless a.bytesize == b.bytesize
l = a.unpack("C#{a.bytesize}")
res = 0
b.each_byte { |byte| res |= byte ^ l.shift }
res == 0
end
end
end
# In your endpoint class
class Api::V1::UserProfile
include Hanami::Action
def call(params)
path = "/api/v1/user/profile"
# Include only cache-significant query parameters
signed_params = params.slice(:user_id, :locale, :currency)
signature = env["HTTP_X_API_SIGNATURE"]
unless Security::HmacValidator.verify_signature(ENV["HMAC_SECRET"], path, signed_params, signature)
self.status = 401
return { error: "invalid_signature" }.to_json
end
# Use signed_params to build cache key and response
cache_key = [path, signed_params].join("|")
# ... fetch or compute response
self.status = 200
{ data: "profile_for_#{signed_params[:user_id]}" }.to_json
end
end
Example 2: Including headers in the signature for cache-sensitive contexts
When headers influence caching (e.g., Accept-Language), include them in the signature to prevent attackers from changing headers to poison the cache.
module Security
class HmacValidator
def self.sign_with_headers(secret, path, params, headers)
data = { path: path, params: params, headers: headers }.to_json
OpenSSL::HMAC.hexdigest("sha256", secret, data)
end
end
end
class Api::V1::LocalizedResource
include Hanami::Action
def call(params)
path = "/api/v1/resource"
selected_headers = { "accept-language" => env["HTTP_ACCEPT_LANGUAGE"] }
signed_params = params.slice(:resource_id)
signature = env["HTTP_X_API_SIGNATURE"]
unless Security::HmacValidator.verify_signature(ENV["HMAC_SECRET"], path, signed_params.merge(selected_headers), selected_headers)
self.status = 401
return { error: "invalid_signature" }.to_json
end
cache_key = [path, signed_params, selected_headers].join("|")
# ... safe cache lookup or compute
self.status = 200
{ data: "resource_for_#{signed_params[:resource_id]}" }.to_json
end
end
Best practices to prevent cache poisoning with Hmac Signatures
- Include all inputs that affect the cache key in the signature: query parameters, selected headers, and the request path.
- Avoid using unsigned parameters to determine cache keys or response content.
- Use a constant-time comparison (e.g.,
secure_compare) to prevent timing attacks on signature verification. - Define a strict allowlist of headers and parameters that participate in the signature and cache key; reject any others.
- Rotate HMAC secrets periodically and store them securely (e.g., environment variables with restricted access).
These steps align with the Input Validation and Authorization checks performed by middleBrick. By ensuring your Hmac Signatures cover the full context used for caching, you reduce the risk of cache poisoning and related data exposure. The scanner’s findings can help you validate that your signatures protect the right inputs.