Bleichenbacher Attack in Feathersjs with Basic Auth
Bleichenbacher Attack in Feathersjs with Basic Auth — how this specific combination creates or exposes the vulnerability
A Bleichenbacher Attack is a cryptographic padding oracle exploit originally described against PKCS#1 v1.5–based encryption or signatures. In a Feathersjs application that uses HTTP Basic Auth, the vulnerability surface is not cryptographic but timing-based: the server performs a username lookup and then compares a supplied password against a stored verifier (hash). If the comparison function does not use a constant-time check, an attacker can learn information about the stored hash one byte at a time by measuring differences in response times or status codes across many requests.
Feathersjs itself does not prescribe how authentication must be implemented; it is common to use local authentication via feathers-authentication-local, which typically hashes passwords with algorithms such as bcrypt. However, if custom authentication logic or an older plugin uses a naive string comparison against a hash or a database field, the service can act as a timing oracle. In such a setup, an attacker sends many forged Basic Auth credentials and observes whether the server returns 200 (success) versus 401/403 (failure). Even when bcrypt is used, a server that leaks information through error handling (e.g., different messages for "user not found" vs "invalid password") or through timing differences in user lookup can be probed similarly to a padding oracle.
Consider a Feathers service that does not standardize its error response and performs a synchronous lookup before hashing. An attacker leveraging the Basic Auth header can iterate over likely usernames and perform byte-by-byte hash comparison timing tests, effectively conducting a Bleichenbacher-style adaptive attack. If the application also exposes unauthenticated endpoints that return distinct responses for missing users, the oracle becomes practical. The risk is compounded when the same service uses predictable user identifiers (e.g., numeric IDs or emails), making enumeration feasible within the 5–15 second scan window that middleBrick uses for black-box testing.
middleBrick’s checks for Authentication, Input Validation, and Rate Limiting are designed to surface indicators that could make a Bleichenbacher-style attack feasible, such as inconsistent authentication timing, missing account lockout, or verbose error messages. While middleBrick does not fix the underlying logic, its findings highlight the need for constant-time comparisons and uniform error handling.
Basic Auth-Specific Remediation in Feathersjs — concrete code fixes
To mitigate timing-based authentication leaks in Feathersjs, ensure that password verification always uses a constant-time comparison and that user existence checks do not leak information. Below are concrete, working examples using the most common Feathers authentication setup with local strategy.
1. Use feathers-authentication-local with proper configuration so that bcrypt handles hashing and verification, and ensure errors are uniform:
// src/authentication.js
const { authentication } = require('@feathersjs/authentication');
const { expressOauth } = require('@feathersjs/authentication-oauth');
const local = require('@feathersjs/authentication-local');
const authentication = local({
entity: 'user',
service: 'users',
secret: process.env.AUTH_SECRET,
usernameField: 'email',
passwordField: 'password',
// Ensure the strategy does not distinguish between "user not found" and "bad password"
errorMessages: {
unknownUser: 'Invalid credentials',
invalidPassword: 'Invalid credentials'
}
});
module.exports = {
authentication
};
2. If you implement custom authentication logic, use a constant-time comparison for any secret material and avoid branching on user existence. Here is an example that hashes the incoming password with the stored hash and uses a timing-safe comparison:
// src/hooks/authenticate-safe.js
const bcrypt = require('bcrypt');
module.exports = function authenticateSafe(options = {}) {
return async context => {
const { email, password } = context.data;
const userService = context.app.service('users');
try {
const user = await userService.get(email); // In practice, use a unique field and catch NotFound
if (!user) {
// Still hash something to keep timing similar
await bcrypt.hash(password, 10);
throw new Error('Invalid credentials');
}
const hash = user.passwordHash;
const isValid = await bcrypt.compare(password, hash);
if (!isValid) {
throw new Error('Invalid credentials');
}
context.result = { userId: user._id };
return context;
} catch (error) {
// Always return a generic error and a consistent delay
await bcrypt.hash(password, 10); // constant-time delay regardless of user existence
throw new Error('Invalid credentials');
}
};
};
3. Apply uniform error handling in your Feathers service to avoid distinguishable responses:
// src/hooks/standard-errors.js
module.exports = function standardErrors() {
return context => {
context.app.hooks = context.app.hooks || {};
context.app.hooks.error = err => {
// Log the real error internally, but return generic messages to the client
return new Error('Invalid credentials');
};
return context;
};
};
4. Enforce rate limiting at the transport or service level to reduce the feasibility of adaptive attacks. In a Feathersjs app using express-rate-limit:
const rateLimit = require('express-rate-limit');
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 50,
message: 'Too many attempts, try again later',
standardHeaders: true,
legacyHeaders: false
});
// In your server setup
app.use('/auth/login', authLimiter);
By combining constant-time verification, uniform error messages, and rate limiting, you reduce the attack surface that would otherwise enable a Bleichenbacher-style adaptive credential test against Basic Auth in Feathersjs.