Cache Poisoning in Express with Hmac Signatures
Cache Poisoning in Express with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Cache poisoning in Express when Hmac Signatures are used occurs when an attacker can cause a cached response to be stored under a URL that differs from the authenticated request used to generate it. This mismatch allows a victim to retrieve attacker-controlled content from the cache, bypassing expected signature validation.
Consider an Express route that caches upstream or CDN responses keyed by the request URL while using an Hmac to verify integrity of selected headers or query parameters. If the cache key is derived from the raw, unvalidated URL (including query string) but the Hmac is computed over a subset of parameters (e.g., excluding an attacker-controlled parameter like redirect_to), an attacker can supply a malicious query parameter that is excluded from the Hmac yet influences the cache key. The server computes and stores a valid Hmac for the canonical set of signed inputs, but the cache entry is keyed with the extra parameter. When the victim requests the same effective path with the attacker-supplied query parameter, the cache serves the poisoned entry because the cache key differs, yet the Hmac verification passes for the signed subset.
In practice, this can lead to authenticated content being served to other users, or cached redirects and dynamic fragments being reused across users. For example, an endpoint /api/widgets signs parameters like widgetId and lang but ignores theme. An attacker crafts /api/widgets?widgetId=123&lang=en&theme=evil. The Hmac is valid for widgetId and lang, but the cache stores a separate entry for the full query string including theme. Later, a victim requesting the signed URL receives the cached response with theme=evil, which the server may interpret as safe because the Hmac matched on the signed subset.
This scenario is especially relevant when the Express app uses a shared cache layer (e.g., a CDN or in-memory cache) and exposes GET endpoints that vary by non-authoritative inputs. The vulnerability is not in Hmac itself, but in the mismatch between cache key construction and the set of inputs covered by the signature. Without server-side cache normalization or explicit exclusion of volatile parameters from cache keys, the attack surface is widened.
To detect this via middleBrick, scanning an Express API that uses Hmac Signatures will surface findings under Data Exposure and Input Validation checks, highlighting inconsistencies between authenticated surface and cached responses. The scan does not modify behavior but provides prioritized findings with remediation guidance to help you address the root cause.
Hmac Signatures-Specific Remediation in Express — concrete code fixes
Remediation centers on ensuring the cache key and the signed inputs are aligned and that cache normalization excludes attacker-influenced parameters from the key. Below are concrete Express examples using Node.js built-in crypto to create and verify Hmac Signatures, with cache-safe practices.
1. Consistent cache key and signed inputs
Derive the cache key only from the parameters included in the Hmac computation. Strip non-authoritative parameters before both signing and caching.
const crypto = require('crypto');
function getSignedParams(url) {
const urlObj = new URL(url, 'http://localhost');
const params = Object.fromEntries(urlObj.searchParams.entries());
// Parameters that must be authenticated
const signedParams = {
widgetId: params.widgetId,
lang: params.lang,
};
return signedParams;
}
function buildCacheKey(signedParams) {
// Stable, canonical key used for both signing and caching
const keys = Object.keys(signedParams).sort();
return keys.map(k => `${k}=${signedParams[k]}`).join('&');
}
function sign(data, secret) {
return crypto.createHmac('sha256', secret).update(data).digest('hex');
}
app.get('/api/widgets', (req, res) => {
const signedParams = getSignedParams(req.url);
const cacheKey = buildCacheKey(signedParams);
const signature = sign(cacheKey, process.env.HMAC_SECRET);
// Use cacheKey for cache lookup/store, and include signature in response headers or a signed query parameter
res.set('X-Content-Signature', signature);
// ... serve response, ensuring cache respects cacheKey
});
2. Verification before cache usage
When serving from cache, verify that the incoming signed parameters match the cached cacheKey and signature. Reject or recompute if mismatch is detected.
app.get('/api/widgets', (req, res, next) => {
const signedParams = getSignedParams(req.url);
const cacheKey = buildCacheKey(signedParams);
const providedSig = req.headers['x-content-signature'];
const computedSig = sign(cacheKey, process.env.HMAC_SECRET);
if (!providedSig || computedSig !== providedSig) {
return res.status(401).send('Invalid signature');
}
// Proceed to cache lookup using cacheKey
// If cache miss, generate and store with cacheKey
next();
});
3. Exclude volatile parameters from cache key and signature
Explicitly remove parameters that should not affect authentication or caching. For example, theme or tracking_id should not be included in the signed set or cache key.
function getSignedParams(url) {
const urlObj = new URL(url, 'http://localhost');
const params = Object.fromEntries(urlObj.searchParams.entries());
// Exclude non-authoritative parameters
delete params.theme;
delete params.tracking_id;
return {
widgetId: params.widgetId,
lang: params.lang,
};
}
4. Use normalized query ordering
Ensure the cacheKey and signature inputs use a canonical ordering of parameters to avoid treating reordered parameters as different cache entries.
function buildCacheKey(signedParams) {
return Object.keys(signedParams)
.sort()
.map(k => `${encodeURIComponent(k)}=${encodeURIComponent(signedParams[k])}`)
.join('&');
}
By aligning cache keys with the exact inputs covered by the Hmac, excluding volatile parameters, and enforcing verification on cache retrieval, you reduce the risk of cache poisoning while preserving performance. middleBrick’s scans can highlight inconsistencies between the authenticated surface and caching behavior, supporting OWASP API Top 10 and compliance mapping.
For teams using the middleBrick ecosystem, the CLI (middlebrick scan <url>) and GitHub Action can integrate these checks into CI/CD, while the Dashboard enables tracking of security scores over time.