Dictionary Attack in Express with Hmac Signatures
Dictionary Attack in Express with Hmac Signatures — how this specific combination creates or exposes the vulnerability
A dictionary attack against an Express API that uses Hmac signatures can be effective when the server-side logic for signature verification leaks information about whether a given secret is partially correct. In Express, an Hmac signature is typically computed over a canonical string—often a combination of selected headers, the request path, the HTTP method, and a timestamp—using a shared secret known only to the client and server. The server recomputes the Hmac with the candidate secret and compares it to the signature sent by the client. If the comparison is performed with a non-constant-time string equality check (e.g., == or .equals() without explicit handling), timing differences can reveal partial matches, enabling an attacker to iteratively refine guesses from a dictionary of likely secrets.
Even when the server uses constant-time comparison, a dictionary attack can still be feasible if the shared secret is weak, reused across multiple endpoints, or derivable from predictable sources. In Express, developers sometimes compute the Hmac over incomplete or structured data (such as only query parameters or only selected headers), which reduces the keyspace and makes offline dictionary attacks practical. For example, an attacker who observes a valid request can extract the signed components and replay them with modified parameters while iterating over candidate secrets. If the API does not enforce strong rate limiting or lacks anti-replay protections (e.g., one-time nonces or short timestamp windows), the attack surface is further expanded. The risk is compounded when the same secret is used for multiple integrations or when the secret is accidentally exposed in client-side code, logs, or error messages.
Another contributing factor is improper handling of timestamp drift and replay windows in Express middleware. If the server accepts a broad range of timestamps without strict validation, an attacker can reuse captured requests within the allowed window while probing secrets from a dictionary. MiddleBrick’s checks for Authentication and BOLA/IDOR, combined with unsafe consumption patterns, can surface these weaknesses when scanning an Express endpoint. Because Hmac signatures rely on shared secrecy, any weakness in secret generation, storage, or comparison logic can undermine the entire scheme. Unlike bearer tokens, Hmac does not inherently provide replay protection; without additional mechanisms—such as nonce tracking or strict one-time use policies—a dictionary attacker can systematically validate candidate secrets against observed traffic without triggering immediate detection.
Real-world attack patterns mirror these concerns: an attacker may intercept a request signed with Hmac-SHA256, extract the signed components, and run a local dictionary using common secrets or breached credential lists. Tools like crypto in Node.js make it straightforward to compute Hmac-SHA256, but insecure implementation choices—such as using a low-entropy secret or skipping canonicalization—can lead to successful compromise. Compliance frameworks such as OWASP API Top 10 and SOC2 highlight the importance of protecting integrity mechanisms and ensuring robust authentication. In scans that include LLM/AI Security checks, MiddleBrick also examines whether endpoint behavior risks leaking information through error messages or inconsistent timing, which could aid an attacker in refining dictionary inputs.
Hmac Signatures-Specific Remediation in Express — concrete code fixes
To defend against dictionary attacks, Express applications should use a constant-time comparison function for Hmac verification and enforce strong, randomly generated secrets. The built-in crypto.timingSafeEqual method should be used to compare the computed digest with the received signature, preventing timing-based leakage. Secrets must be stored securely, rotated periodically, and never shared across unrelated services. Below is a hardened Express route example that demonstrates proper Hmac verification with SHA-256, timestamp validation, and replay protection considerations.
const express = require('express');
const crypto = require('crypto');
const app = express();
const SHARED_SECRET = process.env.HMAC_SHARED_SECRET; // Must be a high-entropy secret
const REPLAY_CACHE = new Set(); // In production, use a distributed cache with TTL
const ALLOWED_CLOCK_SKEW_MS = 30000; // 30 seconds
function verifyHmac(req, res, next) {
const signature = req.headers['x-api-signature'];
const timestamp = req.headers['x-api-timestamp'];
if (!signature || !timestamp) {
return res.status(401).json({ error: 'Missing signature or timestamp' });
}
const now = Date.now();
if (Math.abs(now - parseInt(timestamp, 10)) > ALLOWED_CLOCK_SKEW_MS) {
return res.status(401).json({ error: 'Timestamp outside allowed window' });
}
// Basic replay protection: reject if this timestamp+payload was seen recently
const replayKey = `${timestamp}:${req.method}:${req.path}`;
if (REPLAY_CACHE.has(replayKey)) {
return res.status(401).json({ error: 'Replay detected' });
}
// Build canonical string exactly as the client did
const canonical = `${timestamp}\n${req.method}\n${req.path}\n${req.rawBody}`; // rawBody must be captured via middleware
const computed = crypto.createHmac('sha256', SHARED_SECRET).update(canonical).digest('hex');
const provided = Buffer.from(signature, 'hex');
const expected = Buffer.from(computed, 'hex');
if (provided.length !== expected.length) {
return res.status(401).json({ error: 'Invalid signature' });
}
if (!crypto.timingSafeEqual(provided, expected)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Record this request to prevent immediate replay
REPLAY_CACHE.add(replayKey);
setTimeout(() => REPLAY_CACHE.delete(replayKey), 60000); // TTL aligned with timestamp window
next();
}
app.use(express.raw({ type: '*/*' })); // Ensure raw body is available for canonicalization
app.post('/api/action', verifyHmac, (req, res) => {
res.json({ status: 'ok' });
});
app.listen(3000, () => console.log('Server running on port 3000'));
Additional measures include enforcing a minimum secret length and complexity, rotating secrets using a secure key management process, and adding per-request nonces when higher assurance is required. MiddleBrick’s CLI tool (middlebrick scan <url>) can validate that your Express endpoints use constant-time comparison and inspect whether signatures depend on a sufficient portion of the request. For teams seeking automation, the GitHub Action can integrate these checks into CI/CD pipelines, failing builds if risk scores fall below your defined threshold. The MCP Server enables AI coding assistants to surface insecure verification patterns during development, helping maintain robust Hmac-based authentication as the codebase evolves.
Frequently Asked Questions
Why is constant-time comparison important for Hmac verification in Express?
==) can allow timing side-channels that let an attacker iteratively guess the Hmac secret via a dictionary attack. crypto.timingSafeEqual ensures the comparison takes the same amount of time regardless of how much of the signature matches.