Cache Poisoning in Koa with Hmac Signatures
Cache Poisoning in Koa with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Cache poisoning occurs when an attacker manipulates cached content so that malicious data is served to other users. In Koa, using Hmac Signatures to protect cached responses can inadvertently create or expose risks if the signature is computed over user-controllable inputs without strict validation.
Consider a Koa route that caches HTTP responses based on query parameters. If the cache key includes raw query values and the server signs the cached entry with an Hmac derived from those same values, an attacker can probe for signature behavior while influencing what gets stored in the cache. For example, an endpoint like /api/data?type=report might compute Hmac.sign(type) and use the combination as the cache key. If the application does not canonicalize or strictly validate the type parameter, an attacker can supply values that change the cache key subtly, causing different poisoned entries to be stored and later served to unrelated users.
Another scenario involves caching upstream responses that include sensitive headers or cookies. If the Koa app caches a response that contains an Authorization header or a session cookie and later serves that cached response to other users, the Hmac Signature over the cached payload does not protect the confidentiality of those headers. The signature may validate on the attacker-controlled cache entry, but the content is no longer safe because it was cached based on unvalidated inputs. This becomes especially dangerous when the cache is shared across users or when stale entries are reused without revalidating the context of the original request.
Additionally, if the Hmac key is static and the input includes attacker-influenced data, an attacker can sometimes infer properties about the signing process or reuse a valid signature across manipulated requests. In Koa, middleware that sets cache-related headers such as Cache-Control or uses a caching layer without normalizing inputs can inadvertently allow poisoned cache entries to persist. The Hmac Signature may verify correctly, but the underlying data served from cache can be manipulated to deliver malicious content, redirect users, or expose sensitive information.
Hmac Signatures-Specific Remediation in Koa — concrete code fixes
To mitigate cache poisoning when using Hmac Signatures in Koa, ensure that the signature covers a canonical representation of the data and that cache keys are derived from validated, normalized inputs. Avoid including raw user input directly in cache keys or signature inputs without strict allowlists and normalization.
Below are concrete code examples for secure Hmac usage in Koa.
const Koa = require('koa');
const crypto = require('crypto');
const app = new Koa();
const HMAC_KEY = process.env.HMAC_KEY; // load securely, e.g., from secrets
function safeCacheKey(params) {
// canonicalize: sort keys, use explicit fields, exclude attacker-controlled mutable values
const keys = Object.keys(params).sort();
const parts = keys.map(k => `${k}=${params[k]}`);
return parts.join('&');
}
function signPayload(payload) {
return crypto.createHmac('sha256', HMAC_KEY).update(payload).digest('hex');
}
// Example secured route with canonical cache key and signature verification
app.use(async (ctx) => {
const { type, id } = ctx.query;
// strict allowlist and normalization
const allowedTypes = new Set(['report', 'summary', 'status']);
if (!allowedTypes.has(type)) {
ctx.status = 400;
ctx.body = { error: 'invalid_type' };
return;
}
if (!id || !/^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/i.test(id)) {
ctx.status = 400;
ctx.body = { error: 'invalid_id' };
return;
}
const cacheKey = safeCacheKey({ type, id });
const payload = JSON.stringify({ type, id, data: 'example' }); // deterministic representation
const signature = signPayload(payload);
// Use cacheKey for caching logic; store both payload and signature
// On cache retrieval, verify signature before use
const verified = verifyCachedEntry(cacheKey, signature);
if (verified) {
ctx.body = JSON.parse(verified);
return;
}
// Simulate fetching and caching response
ctx.set('X-Content-Signature', signature);
ctx.body = JSON.stringify({ type, id, data: 'example' });
});
function verifyCachedEntry(key, expectedSignature) {
// pseudo: retrieve cached payload by key, verify Hmac
const cached = getFromCache(key);
if (!cached) return null;
const computed = signPayload(cached.payload);
if (computed !== expectedSignature) return null;
return cached.payload;
}
app.listen(3000);
Key practices:
- Use a strict allowlist for parameters that influence cache keys and signatures.
- Normalize inputs (trim, case normalization, canonical ordering) before including them in the signature or cache key.
- Do not cache responses that contain per-user sensitive headers or cookies; if caching is required, strip or redact sensitive data before storage.
- Treat Hmac Signatures as integrity protection for the cached payload, not as a substitute for proper cache isolation between users.