Rate Limiting Bypass in Express with Hmac Signatures
Rate Limiting Bypass in Express with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Rate limiting in Express is commonly implemented to restrict the number of requests from a client over a time window. When endpoints are protected with Hmac signatures, developers may assume that because each request is authenticated, rate limiting is unnecessary or can be applied after signature verification. This assumption creates a bypass pattern where unsigned or replayed requests can evade limits if rate limiting is not enforced consistently on every request path.
A typical vulnerability arises when the rate limiter is applied only to unauthenticated routes or only after successful Hmac validation. An attacker can send many valid Hmac-signed requests within a short window, exhausting server-side rate limit counters faster than expected if the limiter is scoped per user or per API key rather than per source IP or per endpoint+signature combination. Additionally, if the signature does not include a nonce or timestamp bound to the rate-limiting window, an attacker can replay the same signed request multiple times, and the server may treat each replay as a new request, bypassing intended throttling.
Consider an Express route that verifies an Hmac header but does not enforce a strict request count per client IP within the window used for signature freshness checks. The signature may include a timestamp, but if the server only validates that the timestamp is within an allowed skew (for example, five minutes), an attacker can reuse a valid signature hundreds of times within those five minutes. If the rate limiter uses a sliding window based on user identity rather than request volume per endpoint, the effective limit can be circumvented by distributing requests across multiple signed identities or by piggybacking on a high-privilege token that is not subject to strict limits.
In some configurations, an attacker may also manipulate non-signature parts of the request, such as URL parameters or headers that are excluded from the Hmac calculation, to vary requests slightly while keeping the signature valid. This can fragment the request surface and cause the rate limiter to miscount unique requests, effectively bypassing throttling. For example, if the Hmac is computed over the HTTP method, path, and body, but query parameters used for filtering are excluded, an attacker can cycle through query values while reusing the same signature, causing the limiter to treat each query as a distinct request with no shared counter.
These issues are detectable through black-box scanning that correlates authenticated request patterns with rate-limiting counters. The scanner can send repeated signed requests, inspect response headers for rate-limit indicators, and attempt replay variations to determine whether the effective limit is enforced per logical operation or merely per authentication token. Without including Hmac-bound parameters in the rate-limiting key and ensuring that each distinct request context is uniquely counted, the protection provided by signatures does not translate into effective rate control.
Hmac Signatures-Specific Remediation in Express — concrete code fixes
To secure Express endpoints that use Hmac signatures, rate limiting must be applied uniformly and must incorporate elements that bind each request to a unique, countable context. The following patterns demonstrate how to combine Hmac verification with robust rate limiting.
First, ensure that the Hmac signature covers all inputs that affect rate-limiting decisions, including HTTP method, path, selected query parameters, and a nonce or timestamp that is tightly coupled to the rate-limiting window. Here is an example of computing and verifying an Hmac in Express:
const crypto = require('crypto');
function computeHmac(secret, method, path, timestamp, body) {
const hmac = crypto.createHmac('sha256', secret);
hmac.update(`${method}:${path}:${timestamp}:${body}`);
return hmac.digest('hex');
}
function verifyHmac(req, secret) {
const { timestamp, signature } = req.headers;
const expected = computeHmac(secret, req.method, req.path, timestamp, JSON.stringify(req.body));
// Use timing-safe compare
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
Second, apply rate limiting based on a composite key that includes the client IP and critical parts of the signed request. This prevents an attacker from reusing a signature across many distinct contexts:
const rateLimit = require('express-rate-limit');
const apiLimiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 100,
keyGenerator: (req) => {
// Combine IP with method and path to create unique bucket per operation
return `${req.ip}:${req.method}:${req.path}`;
},
skip: (req) => {
// Skip limit checks if Hmac is invalid; let authentication failures be handled separately
try {
return !verifyHmac(req, process.env.HMAC_SECRET);
} catch (err) {
return true; // skip to avoid leaking timing info
}
},
headers: true,
});
app.use('/api/submit', apiLimiter);
Third, include a short-lived timestamp or nonce within the signed payload and validate its freshness relative to the rate-limiting window. This prevents replay attacks across window boundaries:
function verifyTimestamp(req, maxAgeSeconds = 300) {
const ts = parseInt(req.headers.timestamp, 10);
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - ts) > maxAgeSeconds) {
return false;
}
// Optionally maintain a short denylist of used nonces to prevent replays within the window
return true;
}
Finally, structure your middleware so that rate limiting runs after basic Hmac validation but before heavy processing, and ensure that each distinct signed operation has its own counter. Avoid applying rate limits only at the authentication layer or only on unauthenticated paths. By tying the limit key to the signed context and validating timestamps, you reduce the risk of bypass through replay or parameter manipulation.
Related CWEs: resourceConsumption
| CWE ID | Name | Severity |
|---|---|---|
| CWE-400 | Uncontrolled Resource Consumption | HIGH |
| CWE-770 | Allocation of Resources Without Limits | MEDIUM |
| CWE-799 | Improper Control of Interaction Frequency | MEDIUM |
| CWE-835 | Infinite Loop | HIGH |
| CWE-1050 | Excessive Platform Resource Consumption | MEDIUM |