HIGH cache poisoningkoa

Cache Poisoning in Koa

How Cache Poisoning Manifests in Koa

Cache poisoning in Koa applications typically occurs when user-controlled input influences cache keys or cache storage without proper validation. The most common scenario involves query parameters that affect response content but aren't properly isolated in the cache layer.

Consider a Koa route that serves different content based on a 'theme' parameter:

app.use(async (ctx) => {
  const theme = ctx.query.theme || 'default';
  ctx.body = await renderTemplate(theme);
});

If this response is cached without considering the theme parameter, one user's theme selection could be served to others. The cache key generation becomes critical here—Koa's default behavior doesn't automatically include query parameters in cache keys.

Another Koa-specific manifestation occurs with middleware ordering. If a caching middleware executes before authentication or parameter validation, poisoned data can be stored before security checks complete:

// Problematic ordering
app.use(cacheMiddleware);
app.use(authMiddleware);
app.use(routeHandler);

Cross-site cache poisoning can also occur when Koa applications serve different content types based on Accept headers or other request metadata. An attacker could craft requests that cause the cache to store malicious content under legitimate cache keys.

Koa's flexible middleware system means cache poisoning vectors can appear in unexpected places—custom error handlers, logging middleware, or even response transformers can inadvertently introduce cache poisoning vulnerabilities if they modify responses based on user input.

Koa-Specific Detection

Detecting cache poisoning in Koa requires examining both application code and runtime behavior. Start by auditing middleware ordering and cache key generation logic.

Code review should focus on these patterns:

// Check for missing query parameter handling
app.use(async (ctx) => {
  const data = await getData(ctx.query.id);
  ctx.body = data;
  // Missing: cache key includes query parameters?
});

middleBrick's black-box scanning can identify cache poisoning vulnerabilities by testing how different inputs affect cached responses. The scanner sends multiple requests with varying parameters and checks if responses are incorrectly shared between different user contexts.

Runtime detection involves monitoring cache hit rates and response variations. Tools like koa-conditional-get and koa-etag can help identify when caching behavior doesn't align with content variations.

middleBrick specifically tests for:

  • Query parameter manipulation affecting cached content
  • Header-based content variation without proper cache isolation
  • Authentication state changes not reflected in cache keys
  • Content-Type variations leading to cache poisoning

The scanner's LLM security module also checks for AI-specific cache poisoning where model responses might be cached and served to unauthorized users.

Koa-Specific Remediation

Remediating cache poisoning in Koa requires proper cache key generation and middleware ordering. The most effective approach uses a cache key generator that includes all relevant request parameters:

const createCacheKey = (ctx) => {
  return `${ctx.path}:${JSON.stringify(ctx.query)}:${ctx.accepts()}`;
};

app.use(async (ctx, next) => {
  const cacheKey = createCacheKey(ctx);
  const cached = await cache.get(cacheKey);
  if (cached) {
    ctx.body = cached;
    return;
  }
  await next();
  await cache.set(cacheKey, ctx.body);
});

For applications using koa-router, parameter-based cache keys are essential:

const router = new Router();
router.get('/users/:id', async (ctx) => {
  const userId = ctx.params.id;
  const cacheKey = `user:${userId}`;
  const cached = await cache.get(cacheKey);
  if (cached) {
    ctx.body = cached;
    return;
  }
  const user = await getUser(userId);
  ctx.body = user;
  await cache.set(cacheKey, user);
});

Authentication-aware caching prevents privilege escalation through cached responses:

const authAwareCache = async (ctx, next) => {
  if (!ctx.state.user) {
    // Public content, cache normally
    await next();
  } else {
    // Private content, include user ID in cache key
    const cacheKey = `${ctx.path}:${ctx.state.user.id}`;
    const cached = await cache.get(cacheKey);
    if (cached) {
      ctx.body = cached;
      return;
    }
    await next();
    await cache.set(cacheKey, ctx.body);
  }
};

Content variation handling requires careful cache control headers:

app.use(async (ctx, next) => {
  await next();
  
  // Set appropriate cache headers based on content type
  if (ctx.type === 'application/json') {
    ctx.set('Cache-Control', 'public, max-age=300');
  } else if (ctx.type.startsWith('text/')) {
    ctx.set('Cache-Control', 'public, max-age=300, must-revalidate');
  } else {
    ctx.set('Cache-Control', 'no-cache');
  }
});

Using middleBrick's CLI tool helps verify your remediation:

npx middlebrick scan https://your-koa-app.com/api/users/123?theme=dark

The tool will test if different parameter combinations produce isolated cache entries and identify any remaining poisoning vectors.

Frequently Asked Questions

How does cache poisoning differ from cache injection in Koa?
Cache poisoning occurs when legitimate user input causes incorrect cache storage, while cache injection involves storing malicious content that wasn't part of the original response. In Koa, poisoning often happens through improper cache key generation, whereas injection might occur if response transformers modify content before caching.
Can middleBrick detect cache poisoning in Koa applications?
Yes, middleBrick's black-box scanning tests for cache poisoning by sending requests with varying parameters and checking if responses are incorrectly shared between different contexts. The scanner specifically looks for query parameter manipulation, header-based content variation, and authentication state changes that aren't properly reflected in cache keys.