HIGH timing attackfeathersjsbasic auth

Timing Attack in Feathersjs with Basic Auth

Timing Attack in Feathersjs with Basic Auth — how this specific combination creates or exposes the vulnerability

A timing attack in a Feathersjs service that uses HTTP Basic Auth can occur because password verification is often implemented with a naive string comparison. When comparing the expected password hash or digest with the value derived from the request's Authorization header, an early exit on the first mismatching character can introduce measurable response time differences. An attacker can measure these differences to incrementally learn information about the stored credential. Feathersjs commonly relies on hooks for authentication; if the hook performs the comparison directly in JavaScript without constant-time logic, the variability in processing time becomes observable to a remote network attacker.

In the Basic Auth flow, the client sends an Authorization header formatted as Basic base64(username:password). Feathersjs receives this in a hook, decodes the payload, and must validate the password. If the validation code uses standard equality (e.g., == or ===) on byte sequences or strings, the runtime may stop checking as soon as a mismatch is found. Although Feathersjs itself does not prescribe a particular comparison method, implementations that do not explicitly enforce constant-time comparison expose a measurable difference that can be exploited via statistical analysis across many requests.

The attack surface is further shaped by the unauthenticated scan nature of middleBrick. middleBrick runs 12 security checks in parallel, including Authentication and Input Validation, and it can detect whether timing-related behaviors are present without credentials. By submitting controlled requests and measuring response times, a scanner can infer whether the service responds faster for incorrect usernames versus incorrect passwords, or vice versa. Although middleBrick reports findings and remediation guidance rather than fixing the issue, such observations highlight the importance of implementing constant-time comparison in authentication hooks to mitigate timing-based information leakage.

Basic Auth-Specific Remediation in Feathersjs — concrete code fixes

To mitigate timing attacks in Feathersjs when using Basic Auth, ensure that password verification is performed with a constant-time comparison function. Instead of using native JavaScript equality, use a library that provides constant-time byte comparison, such as crypto.timingSafeEqual available in Node.js. This prevents early exit behavior and ensures that the validation path takes the same amount of time regardless of how much of the input matches.

Below is an example of a Feathersjs authentication hook that decodes the Basic Auth header and performs constant-time verification against a stored hash. The implementation assumes passwords are stored as hashes and uses crypto.timingSafeEqual to compare the derived digest with the stored digest after decoding both to buffers of fixed length.

const crypto = require('crypto');

function timingSafeBufferCompare(a, b) {
  // Ensure both buffers are the same length; if not, compare against a dummy buffer
  const maxLength = Math.max(a.length, b.length);
  const bufA = Buffer.concat([a, Buffer.alloc(maxLength - a.length)]);
  const bufB = Buffer.concat([b, Buffer.alloc(maxLength - b.length)]);
  return crypto.timingSafeEqual(bufA, bufB);
}

function hashPassword(password, salt) {
  // Use a strong, salted derivation; adjust digest and iterations for your security profile
  return crypto.pbkdf2Sync(password, salt, 100000, 64, 'sha512');
}

module.exports = function() {
  const app = this;

  app.hooks.before.push({
    name: 'basic-auth-timing-safe',
    async before(hook) {
      if (hook.method !== 'find' && hook.method !== 'get') {
        return;
      }

      const authHeader = hook.headers.authorization;
      if (!authHeader || !authHeader.startsWith('Basic ')) {
        // Let other hooks handle missing auth; do not reveal timing differences here
        return;
      }

      const base64 = authHeader.slice('Basic '.length);
      let decoded;
      try {
        decoded = Buffer.from(base64, 'base64');
      } catch (error) {
        // Avoid branching on decode errors; return a generic timing-safe failure
        hook.result = { authenticated: false };
        return;
      }

      const separatorIndex = decoded.indexOf(58); // 58 is ':'
      if (separatorIndex <= 0) {
        hook.result = { authenticated: false };
        return;
      }

      const username = decoded.slice(0, separatorIndex).toString('utf8');
      const passwordCandidate = decoded.slice(separatorIndex + 1).toString('utf8');

      // Fetch user record; ensure the query path does not leak timing differences via existence checks
      const user = await app.service('users').get(username).catch(() => null);
      if (!user || !user.passwordHash || !user.salt) {
        // Compute a dummy hash to keep timing consistent
        const dummyHash = hashPassword('dummy', Buffer.alloc(16));
        timingSafeBufferCompare(dummyHash, Buffer.from(user.passwordHash || dummyHash));
        hook.result = { authenticated: false };
        return;
      }

      const candidateHash = hashPassword(passwordCandidate, user.salt);
      const authenticated = timingSafeBufferCompare(candidateHash, user.passwordHash);
      hook.result = { authenticated };

      if (!authenticated) {
        // Return a generic response; avoid signaling which part failed
        hook.result = { authenticated: false };
      }
    }
  });
};

Additional considerations include ensuring that the user lookup path does not introduce timing variance based on whether the username exists. Where possible, use a fixed-duration dummy computation when the user is not found to keep the overall execution time consistent. middleBrick can validate that your service exhibits uniform response characteristics by running its Authentication and Input Validation checks during scans; developers should review the findings and remediation guidance to confirm that constant-time practices are applied consistently across the authentication path.

Frequently Asked Questions

Can middleBrick detect timing-related behaviors during a scan?
Yes. middleBrick runs Authentication and Input Validation checks in parallel and can identify timing-related behaviors by measuring response differences across controlled requests, providing findings and remediation guidance without requiring credentials.
Is using crypto.timingSafeEqual sufficient on its own to prevent timing attacks in Feathersjs?
crypto.timingSafeEqual helps ensure constant-time comparison of buffers, but you must also ensure that surrounding logic (e.g., user lookup, error handling) does not introduce timing variance. Combine constant-time comparison with consistent execution paths and validate your implementation through testing and external scanning.