HIGH cache poisoninghanamihmac signatures

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.

Frequently Asked Questions

How can I verify that my Hmac Signature covers all cache-significant inputs in Hanami?
Include a comprehensive set of inputs (path, query parameters, and selected headers) in the string-to-sign and verify the signature before using those inputs to build a cache key. Use a constant-time comparison and reject any parameters not explicitly allowed.
Does middleBrick test for cache poisoning in Hanami applications?
middleBrick tests for conditions that enable cache poisoning, such as missing input validation and improper authorization. Its BOLA/IDOR and Data Exposure checks can surface risks when Hmac Signatures do not cover cache-influencing parameters.