Password Spraying in Fiber with Api Keys
Password Spraying in Fiber with Api Keys — how this specific combination creates or exposes the vulnerability
Password spraying is an authentication attack that attempts a small number of common passwords across many accounts to avoid account lockouts. When an API built with Fiber exposes authentication via static API keys, the interaction can unintentionally support or amplify spraying patterns. A typical Fiber route may validate an API key in middleware and then load user context without additional rate controls, enabling an attacker to iterate credentials against a single key or enumerate valid usernames.
Consider a login endpoint that accepts an API key in a header and a password in the body. If the key is treated as a long-term credential rather than a rotated secret, and the server does not enforce per-key rate limiting or progressive delays, an attacker can submit many password guesses for that key efficiently. This is particularly risky when the API key is embedded in client-side code or configuration files and is therefore partially exposed. The unauthenticated attack surface tested by middleBrick can identify such endpoints, where authentication is weak or misconfigured, and flag findings like weak authentication and BOLA/IDOR risks.
For example, a route like POST /login that expects X-API-Key plus a password can become an implicit username enumeration mechanism if responses differ based on user existence but not on key validity. Combine this with missing account lockout and the ability to run 5–15 second scans, and an attacker can run low-volume password sprays that evade simple threshold-based defenses. The scanner’s Authentication and Rate Limiting checks highlight these weaknesses, while findings map to OWASP API Top 10 and MITRE ATT&CK techniques relevant to credential abuse.
Api Keys-Specific Remediation in Fiber — concrete code fixes
To reduce the risk of password spraying when using API keys in Fiber, enforce per-key rate limiting, avoid leaking user existence, and rotate keys regularly. Below are concrete code examples that demonstrate secure patterns.
Rate limiting per API key
Use an in-memory or distributed store to track attempts per key and apply delays or rejections when thresholds are exceeded.
import { FastifyInstance, FastifyRequest } from 'fastify';
import Redis from 'ioredis';
const app: FastifyInstance = require('fastify')();
const redis = new Redis();
const RATE_LIMIT_WINDOW_MS = 60_000; // 1 minute
const MAX_REQUESTS_PER_KEY = 30;
app.addHook('onRequest', async (request, reply) => {
const key = request.headers['x-api-key'] as string | undefined;
if (!key) {
reply.code(401).send({ error: 'missing_api_key' });
return;
}
const count = await redis.incr(`ratelimit:${key}`);
if (count === 1) {
await redis.expire(key, Math.ceil(RATE_LIMIT_WINDOW_MS / 1000));
}
if (count > MAX_REQUESTS_PER_KEY) {
reply.code(429).send({ error: 'rate_limit_exceeded' });
return;
}
});
app.post('/login', async (request, reply) => {
const { password } = request.body as { password: string };
// validate password against user bound to key (server-side check)
// ...
reply.send({ ok: true });
});
app.listen({ port: 3000 });
Constant-time response and key rotation
Ensure responses for invalid keys and invalid passwords are consistent in timing and wording to prevent enumeration. Rotate API keys periodically and store them securely, for example using environment variables or a secrets manager.
import { FastifyInstance } from 'fastify';
const app: FastifyInstance = require('fastify')();
// Example of a constant-time comparison stub to avoid timing leaks
function safeCompare(actual: string, expected: string): boolean {
if (actual.length !== expected.length) return false;
let result = 0;
for (let i = 0; i < actual.length; i++) {
result |= actual.charCodeAt(i) ^ expected.charCodeAt(i);
}
return result === 0;
}
app.post('/login', async (request, reply) => {
const key = request.headers['x-api-key'] as string | undefined;
const { password } = request.body as { password: string };
// Retrieve expected key and user password hash securely
const expectedKey = process.env.API_KEY; // rotated regularly
const userHash = getStoredHashForUser(getUserForKey(key)); // abstracted
const keyValid = key && safeCompare(key, expectedKey);
const passwordValid = userHash && verifyPassword(password, userHash);
if (!keyValid || !passwordValid) {
// Always return the same generic response and status
reply.code(401).send({ error: 'invalid_credentials' });
return;
}
reply.send({ ok: true });
});
These measures reduce the effectiveness of password spraying by limiting request volume per key and minimizing information leakage. middleBrick scans can verify that such controls are present by checking authentication and rate limiting configurations alongside runtime behavior.