HIGH cache poisoningstrapihmac signatures

Cache Poisoning in Strapi with Hmac Signatures

Cache Poisoning in Strapi with Hmac Signatures — how this specific combination creates or exposes the vulnerability

Cache poisoning occurs when an attacker manipulates a cached response so that subsequent users receive malicious or incorrect data. In Strapi, when responses are cached based on incomplete or attacker-controlled inputs, and HMAC signatures are used only for verification without proper canonicalization, the cache key may not uniquely represent the expected, authenticated result. This can cause different users’ data or different representations of the same resource to share the same cache entry, effectively leaking information or serving modified content.

Strapi’s admin API and GraphQL endpoints often expose object identifiers and query parameters that, if included in the cache key without considering ownership or tenant boundaries, enable a BOLA/IDOR-like cache poisoning scenario. For example, consider an endpoint that caches user profile data by query parameters but signs the request with an HMAC derived from a subset of parameters (e.g., userId and timestamp). If an attacker can cause the cache to store a response for one user under a cache key that another user’s signed request reuses, the second user may receive the first user’s data. This is especially relevant when Strapi’s caching layer is fronted by a CDN or reverse proxy and query parameters are not consistently normalized.

HMAC signatures are designed to ensure integrity and authenticity, but they do not prevent cache poisoning if the signature does not cover all inputs that affect the cached response. In Strapi, if the signature is computed over the request payload or selected headers but the cache key omits or misrepresents those same inputs, an attacker may induce the server to cache a poisoned response under a key the attacker can force other users to hit. For instance, an unauthenticated SSRF or open redirect that causes Strapi to fetch an attacker-controlled URL and cache the result under a benign-looking key can lead to stored responses being served to other users. The signature may validate correctly if the attacker can influence parts of the request that are included in the HMAC, but the cache may still serve the wrong content because the cache key diverges from the signed scope.

Real-world patterns include missing Vary headers, inconsistent normalization of query parameter ordering, and caching of error responses that reveal existence of internal IDs. These amplify risks when HMAC verification is narrow and does not bind the cache entry to a canonical representation of the request. Because Strapi can integrate with external caching layers, it is important to ensure that the cache key incorporates a stable, normalized set of parameters that are covered by the HMAC and that responses are segregated by tenant and ownership context to avoid cross-user contamination.

Hmac Signatures-Specific Remediation in Strapi — concrete code fixes

To mitigate cache poisoning when using HMAC signatures in Strapi, ensure the signature covers all inputs that affect the cached response and that the cache key includes the same canonicalized inputs. Below are concrete remediation steps and code examples.

  • Include all request dimensions in the HMAC scope: sign a canonical string that combines HTTP method, path, sorted query parameters, and selected headers. Do not omit parameters that affect the response.
  • Normalize query parameters before signing and caching to avoid ordering-based cache key divergence.
  • Bind the cache entry to the user or tenant context so responses are not shared across different authorization contexts.

Example: canonical string construction and HMAC signing in Node.js (Strapi custom middleware or service):

import crypto from 'crypto';

function normalizeParams(params) {
  const keys = Object.keys(params).sort();
  return keys.map(k => `${encodeURIComponent(k)}=${encodeURIComponent(params[k])}`).join('&');
}

function buildCanonicalString(method, path, query) {
  const normalized = normalizeParams(query || {});
  return `${method.toUpperCase()}\n${path}\n${normalized}`;
}

function signPayload(secret, method, path, query) {
  const canonical = buildCanonicalString(method, path, query);
  return crypto.createHmac('sha256', secret).update(canonical).digest('hex');
}

// Example usage in a Strapi request handler
const secret = process.env.HMAC_SECRET; // store securely
const method = ctx.request.method;
const path = ctx.path; // e.g., '/api/users/me'
const query = ctx.request.query;
const signature = signPayload(secret, method, path, query);

// Include the signature in a header for downstream validation or cache key inclusion
ctx.set('X-Request-Signature', signature);
// Use the same canonical string as part of your cache key
const cacheKey = `v1:${method}:${path}:${normalizeParams(query)}:sig:${signature}`;

Example: validating the signature and ensuring cache segregation in a Strapi response middleware:

function verifySignature(secret, method, path, query, receivedSignature) {
  const expected = signPayload(secret, method, path, query);
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(receivedSignature));
}

// In middleware
const received = ctx.request.header['x-request-signature'];
if (!received || !verifySignature(secret, ctx.request.method, ctx.path, ctx.request.query, received)) {
  ctx.throw(401, 'Invalid signature');
}
// Ensure cache key matches the one used during signing
const cacheKey = `v1:${ctx.request.method}:${ctx.path}:${normalizeParams(ctx.request.query)}:sig:${received}`;
// Proceed with cache lookup/store using cacheKey, ensuring Vary headers reflect canonical dimensions

When integrating with caching proxies or CDNs, set Vary headers that include the canonical query string and the user or tenant identifier to prevent cross-context caching. Avoid caching sensitive or user-specific responses unless the cache key and HMAC scope explicitly bind to the authorized subject.

Frequently Asked Questions

Why does including only partial inputs in the HMAC scope still risk cache poisoning in Strapi?
If the HMAC does not cover all inputs that affect the cached response, an attacker can manipulate omitted parameters to cause the server to produce a different response that shares the same signature and cache key. This decouples the signature from the actual cached content and enables cache poisoning.
How can normalization of query parameters prevent cache poisoning in Strapi with HMAC signatures?
Normalization enforces a consistent ordering and encoding of query parameters before signing and caching, ensuring that semantically identical requests always produce the same canonical string, the same HMAC, and the same cache key, thereby eliminating ordering-based cache poisoning.