Cache Poisoning in Koa with Mutual Tls
Cache Poisoning in Koa with Mutual Tls — how this specific combination creates or exposes the vulnerability
Cache poisoning occurs when an attacker manipulates cached responses so that malicious content is served to other users. In Koa, this typically arises through subtle interactions between caching logic (often implemented at a reverse proxy or CDN layer) and the request properties that influence what gets cached. When mutual TLS is used, the server validates client certificates and may use certificate-derived attributes—such as the Common Name (CN), organizational unit, or custom certificate fields—to make routing, tenant, or authorization decisions. If these attributes are reflected in cache keys or response vary headers without proper normalization, two clients with different certificates may inadvertently share cached responses, leading to information leakage or unauthorized content delivery.
For example, a Koa app might use the client certificate’s DN to determine a tenant identifier and include that identifier in the cache key at the infrastructure level. If the application does not ensure that cache keys incorporate a stable, normalized representation of identity, a low-privilege certificate could receive a cached response intended for a higher-privilege tenant. This violates tenant isolation and can expose sensitive data. The unauthenticated attack surface tested by middleBrick can surface such risks by analyzing how vary headers and request attributes interact with caching behavior, even when TLS terminates before the application.
Additionally, mutable request headers that are influenced by the client certificate—such as custom headers injected by the TLS termination layer—can cause inconsistent caching behavior. If a response is cached with a header that should have been excluded (e.g., a certificate-derived user ID), subsequent requests lacking that header may receive a stale or incorrect response. middleBrick’s checks for Data Exposure and Input Validation help identify whether certificate-derived values are improperly reflected in cached outputs, highlighting the need to sanitize and normalize all inputs that influence caching.
Mutual Tls-Specific Remediation in Koa — concrete code fixes
To mitigate cache poisoning risks in Koa with mutual TLS, ensure that cache keys are derived from normalized, application-controlled identifiers rather than raw certificate fields. Explicitly set Vary headers to include only stable request dimensions, and avoid using volatile or identity-sensitive headers in cache lookups. The following patterns illustrate secure approaches.
First, extract and normalize the tenant identifier from the client certificate in a controlled manner, and use it to scope caching without leaking sensitive attributes:
const Koa = require('koa');
const app = new Koa();
// Middleware to extract and normalize tenant from client cert
app.use(async (ctx, next) => {
const cert = ctx.request.socket.getPeerCertificate?.();
// Normalize: use a stable mapping (e.g., a tenant registry) instead of raw DN
const tenantId = mapCertToTenant(cert);
ctx.state.tenantId = tenantId;
await next();
});
// Example mapping function (implement with your tenant registry)
function mapCertToTenant(cert) {
if (!cert || !cert.subject) return 'default';
// Prefer a stable identifier like a SAN or a lookup map; avoid raw CN
const cn = cert.subject.CN || '';
const mapped = cn.startsWith('tenant-') ? cn : 'default';
return mapped;
}
// In a route, use ctx.state.tenantId for tenant-aware logic, but do not rely on it for caching directly
app.use(async (ctx) => {
ctx.body = { tenant: ctx.state.tenantId, data: 'safe-data' };
});
app.listen(3000);
Second, configure caching at the infrastructure level to include a normalized tenant scope in the Vary header while excluding volatile certificate-derived headers:
// Example header normalization before caching (conceptual, depends on your proxy/cdn)
// Ensure Vary includes only stable dimensions such as Accept-Encoding and normalized tenant scope
// Exclude headers like SSL_CLIENT_CERT_DN from cache key generation
Finally, validate and sanitize all inputs that may be influenced by the TLS layer. Treat certificate-derived values as untrusted for cacheability:
// Validate and normalize before use
function sanitizeTenant(input) {
return typeof input === 'string' && input.startsWith('tenant-') ? input : 'default';
}
These practices reduce the risk that certificate-based attributes inadvertently control caching, helping to prevent cross-tenant information exposure.