Request Smuggling in Express with Hmac Signatures
Request Smuggling in Express with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Request smuggling occurs when an application processes HTTP requests differently depending on whether they are interpreted as front-end (e.g., a load balancer or proxy) or back-end (e.g., Express). When Hmac Signatures are used for request authentication but the application does not canonicalize the exact byte sequence used to verify the signature, smuggling becomes possible. The attacker crafts two requests that diverge in how headers or body are parsed by the front-end versus Express, while producing the same Hmac signature for the trusted representation.
In Express, if you compute the Hmac over a subset of headers or a transformed body (e.g., lowercasing header names or normalizing JSON whitespace) but the front-end computes it over the raw request, the front-end may accept a request as valid while Express interprets it differently. For example, a front-end might forward X-Original-URI while Express uses req.url; if the signature is computed without the full path or with a different ordering of query parameters, smuggling can bypass intended routing and authorization checks.
A concrete scenario: an API uses Hmac signatures in headers (X-Signature) and includes a timestamp and a canonical string such as HTTP_METHOD PATH TIMESTAMP BODY_SHA256. If the front-end appends an extra header (like X-Forwarded-Host) that Express ignores when recomputing the canonical string, the signature still matches to the front-end but Express may route the request to a different endpoint or interpret content-length discrepancies differently. This discrepancy enables request smuggling variants such as CL.TE (Content-Length vs. Transfer-Encoding), where the attacker sends Content-Length: 0 and a separate Transfer-Encoding: chunked body. The front-end processes one message length, while Express parses another, allowing a second malicious request to be smuggled in and executed with the same Hmac context.
Because middleBrick tests unauthenticated attack surfaces and includes HTTP protocol-level checks, it can surface inconsistencies between how front-end and back-end interpret requests and signatures. Findings may highlight missing canonicalization, inconsistent header usage, or missing validation of message framing that enable smuggling. The scanner does not fix the implementation but provides prioritized findings with remediation guidance to align signature computation and request parsing across layers.
Hmac Signatures-Specific Remediation in Express — concrete code fixes
Remediation centers on ensuring the exact byte sequence used to compute and verify Hmac is identical across all layers that handle the request. In Express, canonicalize the method, path, selected headers, timestamp, and body before signing and verification. Use a stable header serialization order, avoid including hop-by-hop or proxy-specific headers in the signature base string, and enforce strict message framing so front-end and back-end agree on Content-Length and Transfer-Encoding handling.
Example: canonical string construction and verification in Express using Node.js crypto.
const crypto = require('crypto');
function buildCanonicalString(req) {
const method = req.method.toUpperCase();
const path = req.path; // use req.path, not req.url, to avoid query string ambiguities
const timestamp = req.headers['x-timestamp'];
const bodySha256 = crypto.createHash('sha256').update(req.rawBody || '').digest('hex');
const headers = ['x-request-id', 'x-timestamp', 'content-type'].map(h => h.toLowerCase()).sort().map(h => {
return h + ':' + req.headers[h];
}).join('\n');
return [method, path, timestamp, bodySha256, headers].join('\n');
}
function verifySignature(req, secret) {
const expected = buildCanonicalString(req);
const signature = req.headers['x-signature'];
const actual = crypto.createHmac('sha256', secret).update(expected).digest('hex');
return crypto.timingSafeEqual(Buffer.from(actual), Buffer.from(signature));
}
app.use((req, res, next) => {
// Ensure rawBody is available (e.g., via a body parser that preserves bytes)
if (!verifySignature(req, process.env.HMAC_SECRET)) {
return res.status(401).json({ error: 'invalid_signature' });
}
next();
});
Example: Express route using the verification middleware and ensuring consistent body handling.
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
// req.body is a Buffer; convert for business logic after verification
const bodyObj = JSON.parse(req.body.toString('utf8'));
// process bodyObj
res.json({ received: true });
});
Additional measures: explicitly set Content-Length and avoid chunked transfer encoding when operating across layers with different parsing behavior; reject requests with both Content-Length and Transfer-Encoding (CL.TE mitigation); normalize header names to lowercase and sort them deterministically; include a request ID and timestamp in the canonical string to prevent replay; and use a stable separator (e.g., newline) that cannot appear in header values without proper escaping. These steps reduce the risk that front-end and back-end interpretations diverge, limiting opportunities for request smuggling while preserving the integrity of Hmac-based authentication.