HIGH password sprayingexpressapi keys

Password Spraying in Express with Api Keys

Password Spraying in Express with Api Keys — how this specific combination creates or exposes the vulnerability

Password spraying is an authentication technique where an attacker uses a small number of commonly used passwords against many user accounts to avoid account lockouts. When an Express API relies solely on API keys for authorization without additional controls, password spraying can still present a risk if the API exposes user enumeration or if API keys are used in combination with weak account passwords.

In Express, a typical setup might validate an API key via a header (e.g., x-api-key) and then associate that key with a user or scope. If the endpoint does not enforce rate limiting and returns different responses for valid versus invalid keys, attackers can iterate passwords across known or guessed usernames or client IDs. Even though API keys themselves are not passwords, poor key management or key-to-account mappings can enable password spraying when attackers test common passwords against accounts that are weakly protected or where keys are rotated infrequently.

Consider an endpoint that accepts both an API key and a password-based login token. Without adequate rate limiting, an attacker can submit many password guesses per key or across multiple keys. If the API reveals whether a username exists (user enumeration) via timing differences or distinct error messages, attackers can refine their spraying campaigns. Findings from middleBrick scans often highlight weak rate limiting and inconsistent responses as precursors to successful spraying. The tool’s checks for Authentication and Rate Limiting, combined with BFLA/Privilege Escalation and Input Validation, can surface these issues before attackers do.

Real-world attack patterns include using common passwords like Password1 or Welcome2025 across a list of known API keys or client IDs. If the API does not enforce per-client or per-user attempt limits, attackers can iterate thousands of attempts with minimal noise. Because API keys are long-lived credentials, reused or weakly rotated keys increase the window of exposure. middleBrick’s LLM/AI Security checks are not designed to detect password spraying, but its Authentication and Rate Limiting tests help identify the conditions that make spraying practical.

To illustrate a vulnerable Express route, imagine a login endpoint that accepts an API key and a password without sufficient throttling:

const express = require('express');
const app = express();
app.use(express.json());

const API_KEYS = new Set(['abc123', 'def456']);
const users = {
  'alice': { password: 'Summer2024!', key: 'abc123' },
  'bob': { password: 'Winter2023#', key: 'def456' }
};

app.post('/login', (req, res) => {
  const { apiKey, username, password } = req.body;
  if (!apiKey || !username || !password) {
    return res.status(400).json({ error: 'Missing credentials' });
  }
  if (!API_KEYS.has(apiKey)) {
    return res.status(401).json({ error: 'Invalid API key' });
  }
  const user = users[username];
  if (!user) {
    return res.status(401).json({ error: 'Invalid credentials' });
  }
  if (user.password !== password || user.key !== apiKey) {
    return res.status(401).json({ error: 'Invalid credentials' });
  }
  res.json({ token: 'fake-jwt-token' });
});

app.listen(3000, () => console.log('Server running on port 3000'));

This example lacks rate limiting and consistent error messaging, making it susceptible to password spraying across valid API keys. An attacker with a valid API key could iterate many passwords for known users. middleBrick scans can detect missing rate limiting and inconsistent status codes, guiding remediation.

Api Keys-Specific Remediation in Express — concrete code fixes

Remediation focuses on removing password dependencies where only API keys should grant access, enforcing rate limits, and ensuring consistent error handling. When API keys are the primary credential, avoid mixing them with passwords or treat passwords as secondary only for initial setup.

First, enforce rate limiting per API key or per IP using a middleware like express-rate-limit. This reduces the effectiveness of spraying by capping attempts:

const rateLimit = require('express-rate-limit');

const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // limit each API key/IP to 100 requests per window
  keyGenerator: (req) => req.body.apiKey || req.ip,
  message: { error: 'Too many requests, try again later.' }
});
app.use('/login', apiLimiter);

Second, standardize error responses to avoid user enumeration. Return the same status code and generic message for invalid keys, missing users, or incorrect passwords:

app.post('/login', (req, res) => {
  const { apiKey, username, password } = req.body;
  if (!apiKey || !username || !password) {
    return res.status(401).json({ error: 'Invalid credentials' });
  }
  if (!API_KEYS.has(apiKey)) {
    return res.status(401).json({ error: 'Invalid credentials' });
  }
  const user = users[username];
  if (!user) {
    return res.status(401).json({ error: 'Invalid credentials' });
  }
  if (user.password !== password || user.key !== apiKey) {
    return res.status(401).json({ error: 'Invalid credentials' });
  }
  res.json({ token: 'fake-jwt-token' });
});

Third, if passwords are retained for legacy reasons, store them with strong adaptive hashing (e.g., bcrypt) and avoid using them in authorization decisions when an API key is present:

const bcrypt = require('bcrypt');
const SALT_ROUNDS = 12;

async function verifyUser(username, password, apiKey) {
  const user = users[username];
  if (!user || user.key !== apiKey) {
    return false;
  }
  return await bcrypt.compare(password, user.hashedPassword);
}

app.post('/login', async (req, res) => {
  const { apiKey, username, password } = req.body;
  if (!apiKey || !username || !password) {
    return res.status(401).json({ error: 'Invalid credentials' });
  }
  const valid = await verifyUser(username, password, apiKey);
  if (!valid) {
    return res.status(401).json({ error: 'Invalid credentials' });
  }
  res.json({ token: 'fake-jwt-token' });
});

For production, rotate API keys regularly and avoid long-lived static keys. middleBrick’s CLI can be integrated into scripts to scan endpoints and verify that authentication and rate limiting checks pass. The GitHub Action can enforce a maximum risk score threshold in CI/CD, preventing deployments that introduce weak authentication patterns.

Frequently Asked Questions

Can password spraying occur if only API keys are accepted and passwords are disabled?
If passwords are fully disabled and only cryptographically random, high-entropy API keys are required, password spraying is not applicable. However, weak or leaked API keys can still be brute-forced; enforce rate limits and rotate keys to reduce risk.
Does middleBrick fix password spraying issues automatically?
middleBrick detects and reports findings with remediation guidance, but it does not fix, patch, block, or remediate. Use its reports to adjust rate limiting, error handling, and credential storage in your Express code.