Timing Attack in Feathersjs
How Timing Attack Manifests in Feathersjs
In a Feathersjs application, timing attacks often appear in places where secret values are compared using non‑constant‑time operations. A common pattern is a custom authentication hook that checks a user‑supplied token or API key against a stored value with a simple JavaScript equality check (===) or ==. Because the comparison stops at the first mismatched character, an attacker can measure response times and gradually guess the secret.
For example, a Feathersjs service that protects an endpoint with a static API key might look like this:
// src/hooks/check-api-key.js
const crypto = require('crypto');
module.exports = function () {
return async function (context) {
const provided = context.params.query.api_key;
const expected = process.env.API_KEY; // <-- secret
// Vulnerable: non‑constant‑time string compare
if (provided === expected) {
return context;
}
throw new Error('Invalid API key');
};
};
When an attacker sends requests with different API key guesses, the server returns faster for guesses that mismatch early in the string and slower when they match more characters. Over many requests the attacker can reconstruct the key.
Timing issues also appear in password verification when developers use bcrypt.compare incorrectly (e.g., wrapping it in a custom timeout) or when they implement their own hash comparison. Feathersjs’ built‑in authentication (@feathersjs/authentication) avoids this by using constant‑time libraries, but any custom hook or service method that re‑implements secret comparison can reintroduce the risk.
Feathersjs-Specific Detection
Detecting a timing vulnerability in a Feathersjs API requires measuring minute differences in response time between valid and invalid inputs. Because the difference can be only a few microseconds, a single request is insufficient; you need many samples and statistical analysis.
middleBrick includes timing attack checks as part of its Input Validation and Authentication categories. When you submit a URL to the scanner, it:
- Sends a series of requests with gradually changing input (e.g., API key guesses) while keeping all other parameters constant.
- Records the response time for each request.
- Applies a statistical test (e.g., Student’s t‑test) to determine whether timing correlates with input correctness.
- If a significant correlation is found, it reports a finding with severity based on the observed timing delta and the ease of exploitation.
You can run the same check locally with the middleBrick CLI:
npx middlebrick scan https://api.example.com/protected
The CLI will output a JSON report that includes a timing_attack finding under the authentication section, showing the measured timing difference and the number of probes used.
Because middleBrick works unauthenticated and black‑box, it does not need any Feathersjs‑specific configuration; it treats the API as any HTTP endpoint and looks for timing leakage in the responses it receives.
Feathersjs-Specific Remediation
The fix is to replace any non‑constant‑time secret comparison with a constant‑time alternative. In Node.js the standard library provides crypto.timingSafeEqual, which compares two Buffers without leaking timing information.
Here is the previous API‑key hook rewritten to use a constant‑time check:
// src/hooks/check-api-key-fixed.js
const crypto = require('crypto');
module.exports = function () {
return async function (context) {
const provided = context.params.query.api_key;
const expected = process.env.API_KEY;
// Convert to Buffers; timingSafeEqual works on Buffers only
const providedBuf = Buffer.from(provided, 'utf8');
const expectedBuf = Buffer.from(expected, 'utf8');
if (crypto.timingSafeEqual(providedBuf, expectedBuf)) {
return context;
}
throw new Error('Invalid API key');
};
};
If you are verifying passwords, continue to use bcrypt.compare (which is already constant‑time) and avoid wrapping it in custom timeouts or logging that could reintroduce variance.
Feathersjs makes it easy to apply this hook globally or to specific services. For example, to protect all find and get calls on a messages service:
// src/services/messages/messages.hooks.js
const { authenticate } = require('@feathersjs/authentication').hooks;
const checkApiKey = require('../../hooks/check-api-key-fixed');
module.exports = {
before: {
all: [ authenticate('jwt'), checkApiKey() ],
find: [],
get: [],
create: [],
update: [],
patch: [],
remove: []
},
after: { ... },
error: { ... }
};
After deploying the fix, run middleBrick again. The timing attack finding should disappear (or be downgraded to informational), confirming that the secret comparison now runs in constant time.