HIGH cache poisoningkoaapi keys

Cache Poisoning in Koa with Api Keys

Cache Poisoning in Koa with Api Keys — how this specific combination creates or exposes the vulnerability

Cache poisoning occurs when an attacker manipulates a cache so that malicious content is served to other users. In Koa, this can arise when API keys or other request attributes are used to vary cache keys without proper validation or normalization. If a Koa application includes raw query parameters or headers such as api_key directly in the cache key, an attacker can force different responses to be cached under the same key, leading to one user receiving another user’s data or poisoned content.

Consider a scenario where a public endpoint uses a query parameter api_key for identification but does not sanitize or scope it before caching. An attacker could supply a crafted api_key that causes the application to cache a response containing sensitive information or malicious script. Because the cache key includes the api_key value, other users requesting the same path might inadvertently receive the attacker’s cached response, exposing private data or enabling stored XSS in the context of API responses.

This risk is compounded when the Koa app proxies requests to backend services and caches based on a combination of path and selected headers without stripping or hashing sensitive values such as api_key. If the api_key is treated as part of the cache key without normalization, two requests that are functionally identical but differ only in the api_key value will create separate cache entries, potentially bypassing intended isolation and enabling cross-user cache contamination.

Api Keys-Specific Remediation in Koa — concrete code fixes

To mitigate cache poisoning related to API keys in Koa, avoid using raw API key values directly in cache keys. Instead, hash or omit sensitive identifiers, and ensure cache normalization aligns with the intended scope of cached data. Below are concrete code examples for a Koa-based API using API keys safely.

Example 1: Removing api_key from cache key

Strip the api_key query parameter before constructing the cache key, ensuring that the cache is shared only where appropriate.

const Koa = require('koa');
const crypto = require('crypto');
const app = new Koa();

app.use(async (ctx, next) => {
  // Remove api_key from query before caching logic
  const { api_key, ...queryWithoutKey } = ctx.query;
  const queryString = new URLSearchParams(queryWithoutKey).toString();
  const cacheKey = crypto.createHash('sha256').update(ctx.path + '?' + queryString).digest('hex');

  const cached = await getFromCache(cacheKey);
  if (cached) {
    ctx.body = cached;
    return;
  }

  await next();

  // Store response in cache using normalized key
  await setInCache(cacheKey, ctx.body);
});

// Dummy cache functions
async function getFromCache(key) { /* ... */ }
async function setInCache(key, value) { /* ... */ }

app.listen(3000);

Example 2: Hashing api_key to scope cache per user without exposing raw key

Use a salted hash of the API key to scope cached data to a particular consumer while preventing key leakage through cache entries.

const Koa = require('koa');
const crypto = require('crypto');
const app = new Koa();

// Salt should be stored securely, e.g., from environment
const CACHE_SALT = process.env.CACHE_SALT || 'static_salt_rotate_me';

app.use(async (ctx, next) => {
  const rawKey = ctx.query.api_key || ctx.request.headers['x-api-key'] || '';
  const userScope = crypto.createHmac('sha256', CACHE_SALT)
                          .update(rawKey)
                          .digest('hex');

  const cacheKey = `v1:${userScope}:${ctx.path}:${JSON.stringify(ctx.query)}`;

  const cached = await getFromCache(cacheKey);
  if (cached) {
    ctx.body = cached;
    return;
  }

  await next();

  await setInCache(cacheKey, ctx.body);
});

// Dummy cache functions
async function getFromCache(key) { /* ... */ }
async function setInCache(key, value) { /* ... */ }

app.listen(3000);

Best practices

  • Never include raw API keys in cache keys; use normalized forms or hashes.
  • Validate and normalize query parameters and headers before using them to determine cache scope.
  • Scope caches per consumer where necessary, but ensure that scoping does not inadvertently mix data between consumers.

Frequently Asked Questions

Why is including raw api_key values in cache keys risky?
Including raw API keys in cache keys can cause cache collisions across users and may expose one user's cached data to another. It can also lead to keys being logged or persisted unintentionally, increasing the risk of leakage.
Does hashing the api_key fully protect against cache poisoning?
Hashing reduces the risk of exposing raw keys and helps scope caches, but cache poisoning can still occur through other vectors such as unvalidated input that affects response content. Always normalize and validate all inputs that influence cache behavior.