HIGH timing attackexpressapi keys

Timing Attack in Express with Api Keys

Timing Attack in Express with Api Keys — how this specific combination creates or exposes the vulnerability

A timing attack in an Express API that uses API keys can occur when the server compares the client-supplied key with the stored key in a way whose execution time depends on the key value. In JavaScript, a naive string comparison such as a === b is not constant-time; it short-circuits and returns as soon as a mismatching character is found. An attacker can measure response times to infer how many initial characters of the submitted key match the expected key, gradually learning the correct key byte by byte.

Express applications often validate API keys in middleware. If the middleware retrieves a key from a data store (e.g., a database or in-memory map) and compares it directly using JavaScript equality, the comparison time varies with key similarity. For example, an endpoint like /admin that checks if (key === storedKey) will be faster for a key that mismatches at the first character and slower when more characters match. By sending many requests with keys that differ slightly and measuring latency, an attacker can deduce the correct key. This is particularly relevant when the API key is static or long-lived, as the attacker can profile the server without triggering frequent credential rotation.

Real-world attack patterns mirror known weaknesses in authentication logic. For instance, if an Express route uses a simple string comparison instead of a constant-time check, an attacker may combine timing observations with other vulnerability categories that middleBrick tests, such as Authentication and BOLA/IDOR, to amplify risk. Even without leaking the key directly, timing differences can reveal whether a key exists in a given set or which prefix is correct, enabling account enumeration or unauthorized access. Because the attack is network-based and relies on statistical analysis of response times, it does not require access to server code or infrastructure—only the ability to send requests and measure responses.

To illustrate the vulnerability, consider the following Express middleware with an unsafe comparison:

const express = require('express');
const app = express();

const apiKeys = new Set(['s3cr3tk3y123', 'a1b2c3d4e5']);

app.use((req, res, next) => {
  const key = req.get('x-api-key');
  if (!key) return res.status(401).send('Missing key');
  if (apiKeys.has(key)) return next();
  res.status(403).send('Invalid key');
});

app.get('/admin', (req, res) => {
  res.send('Admin area');
});

app.listen(3000);

In this code, Set.prototype.has uses strict equality internally, which short-circuits on the first mismatching character. An attacker can exploit this by sending candidate keys and observing whether responses are marginally faster, revealing partial matches. The issue is not the use of API keys itself, but the lack of a constant-time comparison and additional protections like rate limiting that could mitigate timing-based inference.

Api Keys-Specific Remediation in Express — concrete code fixes

Remediation focuses on ensuring that key comparisons execute in constant time, regardless of how closely the submitted key matches the stored key. In Node.js, the built-in crypto.timingSafeEqual function can be used to compare buffers of equal length without early exit. Because API keys are typically strings, they should be converted to buffers of fixed length; if lengths differ, a fixed-length comparison should still be performed by comparing against a stored hash or using a fixed-size buffer padded with known values.

Additionally, defense-in-depth measures reduce the feasibility of timing attacks. Introducing small, random delays can obscure timing differences, though this is a weak mitigation on its own. More robust approaches include using constant-time hash comparisons (e.g., comparing HMACs derived from the key) and ensuring that API key validation does not leak information via other channels. MiddleBrick scans can help identify whether an endpoint is vulnerable by checking for inconsistent response times across requests, a capability included in its 12 security checks.

Below is a secure Express middleware example using crypto.timingSafeEqual and fixed-length buffers:

const express = require('express');
const crypto = require('crypto');
const app = express();

// Store keys as fixed-length buffers (e.g., 32 bytes)
// In practice, derive these from a secure source and keep them constant-length.
const storedKeyBuffer = Buffer.from('s3cr3tk3y123abc000000000000', 'utf8');
const alternativeKeyBuffer = Buffer.from('a1b2c3d4e5f60000000000000000', 'utf8');

function safeKeyCompare(candidate) {
  const candidateBuffer = Buffer.from(candidate, 'utf8');
  // Ensure buffers are the same length; if not, use a fixed-length comparison
  if (candidateBuffer.length !== storedKeyBuffer.length) {
    // Perform a dummy comparison to keep timing consistent
    const dummyBuffer = Buffer.alloc(storedKeyBuffer.length);
    crypto.timingSafeEqual(storedKeyBuffer, dummyBuffer);
    return false;
  }
  return crypto.timingSafeEqual(candidateBuffer, storedKeyBuffer) ||
         crypto.timingSafeEqual(candidateBuffer, alternativeKeyBuffer);
}

app.use((req, res, next) => {
  const key = req.get('x-api-key');
  if (!key) return res.status(401).send('Missing key');
  if (safeKeyCompare(key)) return next();
  res.status(403).send('Invalid key');
});

app.get('/admin', (req, res) => {
  res.send('Admin area');
});

app.listen(3000);

For a more scalable approach, consider using a constant-time comparison library or storing key hashes (e.g., SHA-256) and comparing hashes with crypto.timingSafeEqual. MiddleBrick’s CLI can be used to validate that your endpoints no longer exhibit timing variance indicative of weak comparisons, and its GitHub Action can enforce secure practices in CI/CD pipelines.

Frequently Asked Questions

Can a timing attack retrieve the full API key from an Express server?
Yes, if the comparison leaks timing information and the key is static, an attacker can iteratively learn the key byte by byte. Using constant-time comparisons eliminates this vector.
Does adding random delays fully prevent timing attacks on API key validation?
No. While random delays can obscure timing differences, they are not reliable protection. Constant-time algorithms and secure comparison primitives are necessary to prevent inference attacks.