HIGH cache poisoningfeathersjsbasic auth

Cache Poisoning in Feathersjs with Basic Auth

Cache Poisoning in Feathersjs with Basic Auth — how this specific combination creates or exposes the vulnerability

Cache poisoning occurs when an attacker causes a shared cache to store malicious responses that are then served to other users. In Feathersjs applications that use Basic Auth over unencrypted channels or leak credentials via query strings, this risk is amplified because authentication data can become part of the cache key or be reflected in cached error responses.

Feathersjs services often rely on REST transports and may expose endpoints that accept user-controlled query parameters. When Basic Auth credentials are passed via the Authorization header, some proxy or CDN caches can incorrectly normalize requests by including headers in cache keys or by mishandling Authorization, leading to one user’s authenticated response being cached and served to another. Additionally, if Feathersjs services return detailed errors when authentication credentials are invalid, those error pages may be cached and returned to subsequent users, potentially exposing stack traces or internal paths.

Consider a Feathersjs service with an endpoint /api/profile that uses Basic Auth. If a caching layer uses the full request URL—including query parameters—and the client sends credentials via the header but the server also reflects the username in the response body, an attacker could craft URLs with different usernames in query strings to probe whether responses are being cached. In misconfigured setups, an attacker might also manipulate Authorization header values to trigger 401 responses that get cached, resulting in a scenario where legitimate users receive cached 401 pages instead of valid content.

The combination of Basic Auth and caching is particularly sensitive because the Authorization header should prevent caching per standard HTTP semantics; however, intermediaries that do not strip or properly handle Authorization can break this expectation. For example, a shared cache that does not differentiate by headers may store a response from one authenticated user and serve it to another, effectively leaking one user’s data to another. This violates the principle that authenticated responses must not be shared across users.

Basic Auth-Specific Remediation in Feathersjs — concrete code fixes

To mitigate cache poisoning when using Basic Auth in Feathersjs, ensure that authenticated responses are never cached by intermediaries and that request normalization excludes sensitive headers. The following practices and code examples help secure Feathersjs services.

1. Configure Feathersjs service to set no-cache headers for authenticated responses

Use a hook to add HTTP headers that prevent caching when authentication is present. This ensures caches do not store responses tied to a specific credential.

// src/hooks/no-cache-auth.js
module.exports = function noCacheAuthHook(options = {}) {
  return async context => {
    if (context.params && context.params.headers && context.params.headers.authorization) {
      // Prevent caching of authenticated responses
      context.result = context.result || {};
      // If using a REST transport, set headers via context
      if (context.app && typeof context.app.set === 'function') {
        // For Feathers REST, headers are typically set in the transport layer.
        // This example assumes you have access to the response object.
        // A more reliable approach is to set headers in the after hook.
      }
    }
    return context;
  };
};

// In your service file
const noCacheAuth = require('./hooks/no-cache-auth');
app.use('/api/profile', service());
app.service('api/profile').hooks({
  after: [noCacheAuth()]
});

2. Use a dedicated REST hook to set Cache-Control and Vary headers

Feathersjs allows you to set response headers in an after hook. This example sets Cache-Control: no-store when Basic Auth is used, preventing storage of sensitive responses.

// src/hooks/cache-control-auth.js
module.exports = function cacheControlAuthHook(options = {}) {
  return async context => {
    const { headers } = context.params || {};
    if (headers && headers.authorization && /^Basic\s+/i.test(headers.authorization)) {
      // Ensure response headers prevent caching
      context.result = context.result || {};
      // Set headers appropriately for your transport; this example targets REST
      if (context.app && context.app.set) {
        // Typically, you set headers via the transport. For REST:
        const res = context.app.get('server')._events; // Not recommended; see note below
      }
    }
    return context;
  };
};

// Better: Use the REST transport's after handler directly if available
// Many deployments use an after hook that has access to the HTTP response object.
// Example for feathers-rest:
const feathers = require('@feathersjs/feathers');
const rest = require('@feathersjs/rest');
const app = feathers().configure(rest());

app.use('/secure', service());
app.service('secure').hooks({
  after: [context => {
    if (context.params && context.params.headers && /Basic\s+\S+/.test(context.params.headers.authorization || '')) {
      // Set no-store header
      context.params.res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, private');
      context.params.res.setHeader('Vary', 'Authorization');
    }
    return context;
  }]
});

3. Avoid exposing usernames in URLs or error messages

Ensure that error responses do not include authentication details. Standardize error messages to avoid leaking whether a username exists. Also, avoid having the username appear in URLs query strings when using Basic Auth.

// src/hooks/sanitize-errors.js
module.exports = function sanitizeErrorsHook(options = {}) {
  return async context => {
    if (context.error) {
      // Replace detailed auth errors with generic messages
      if (context.error.message && /authorization|auth|basic/i.test(context.error.message)) {
        context.error.message = 'Unauthorized';
        context.error.statusCode = 401;
      }
    }
    return context;
  };
};

app.service('api/profile').hooks({
  error: [sanitizeErrorsHook()]
});

4. Enforce HTTPS and remove credentials from URLs

Always use HTTPS to prevent credentials from being intercepted. Configure your server to redirect HTTP to HTTPS and ensure that Basic Auth credentials are only sent over encrypted connections. Do not include credentials in URLs (e.g., https://user:[email protected]/api), as they may be logged in server logs and cache histories.

// Example server redirect (conceptual, depends on your server setup)
// In production, use your web server (e.g., Nginx, Caddy) to enforce HTTPS.
// MiddleBrick scans can verify that your API enforces HTTPS and does not accept credentials via query strings.

Frequently Asked Questions

Can cached error responses from Basic Auth endpoints leak sensitive information?
Yes. If error responses containing authentication failure details are cached and served to other users, they may reveal internal paths or account existence. Use hooks to standardize errors and set no-store headers when Basic Auth is present.
How does middleBrick help detect cache poisoning risks in Feathersjs APIs using Basic Auth?
middleBrick scans unauthenticated attack surface and maps findings to frameworks like OWASP API Top 10. It checks for missing cache-control headers and reflected credentials in responses, providing prioritized findings and remediation guidance without storing or fixing issues.