Header Injection in Express with Hmac Signatures
Header Injection in Express with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Header Injection in Express applications that rely on Hmac Signatures for request integrity occurs when untrusted input from headers, query parameters, or the request body is used to influence the set of headers that are included in the Hmac computation or transmitted to downstream services. Because Hmac signatures typically cover selected headers and a canonical representation of the message, injecting additional or malformed headers can shift which headers are signed, enable header smuggling, or bypass validation logic that incorrectly trusts the presence or absence of certain headers.
In Express, a common pattern is to compute an Hmac over selected request headers plus a payload and then verify that Hmac on the server or before forwarding to a backend. If the application uses user-supplied header names or values as part of the signature inputs without strict allowlisting, an attacker can inject extra headers such as x-forwarded-for, x-real-ip, or custom headers that affect routing, authentication, or logging. These injected headers may not be covered by the signature verification logic on the peer system, leading to desynchronization between what the signer expected and what the verifier sees. This can cause the signature to be accepted erroneously or rejected incorrectly, undermining integrity checks and potentially enabling request tampering or privilege escalation.
Another vector specific to Express middleware is header manipulation via libraries that normalize or forward headers. For example, an attacker may inject X-Forwarded-Proto or X-Forwarded-Host to influence URL reconstruction or TLS assumptions later in the pipeline. If the Hmac signature is computed before these values are overridden by trusted proxies but verified after they are applied, the mismatch can be exploited to bypass intended integrity checks. Similarly, duplicate or malformed headers may cause inconsistent behavior across Node.js versions and HTTP server implementations, which can lead to signature validation passing when it should fail or failing when it should pass, depending on how the framework parses and exposes headers.
These issues are not theoretical; they map to common weaknesses enumerated in the OWASP API Security Top 10, such as Improper Validation of Input and Broken Authentication under certain conditions. While Hmac Signatures provide strong integrity guarantees when implemented correctly, their security depends on strict control over which headers and values are included in the signed payload, and on rejecting or sanitizing any user-influenced headers before verification. Without such controls, Header Injection undermines the trust boundary that Hmac Signatures are meant to enforce.
Hmac Signatures-Specific Remediation in Express — concrete code fixes
To remediate Header Injection risks in Express when using Hmac Signatures, you must strictly control which headers participate in signing, canonicalize header names and values, and avoid incorporating untrusted input into the signed material. Below are concrete, realistic code examples that demonstrate a secure approach.
const crypto = require('crypto');
const express = require('express');
const app = express();
const SHARED_SECRET = process.env.HMAC_SECRET; // store securely
function buildCanonicalHeaders(headers, allowedHeaderNames) {
return allowedHeaderNames
.map((name) => {
const key = name.toLowerCase(); // normalize to lowercase
const value = headers[key] || '';
return `${key}:${value}`;
})
.join('\n');
}
function computeRequestHmac(headers, body, allowedHeaderNames) {
const canonical = buildCanonicalHeaders(headers, allowedHeaderNames) + '\n' + body;
return crypto.createHmac('sha256', SHARED_SECRET).update(canonical, 'utf8').digest('base64');
}
const ALLOWED_HEADERS = ['content-type', 'x-request-id', 'x-timestamp'];
app.use(express.json());
app.use((req, res, next) => {
const body = req.rawBody; // ensure raw body is available, e.g., via a body parser that preserves buffers
const expected = req.headers['x-hmac'];
const computed = computeRequestHmac(req.headers, body, ALLOWED_HEADERS);
if (!expected || !crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(computed))) {
return res.status(401).json({ error: 'invalid_hmac' });
}
next();
});
app.post('/api/resource', (req, res) => {
res.json({ received: req.body });
});
app.listen(3000, () => console.listener('Server listening on port 3000'));
This example ensures only a predefined allowlist of headers is included in the Hmac computation, normalizes header names to lowercase, and uses timing-safe comparison. It avoids incorporating any attacker-controlled header names or values into the signature material, which prevents Header Injection via malformed or duplicate headers. For production use, you should also enforce strict header normalization and reject requests with unexpected or duplicate header instances that could confuse downstream proxies.
When integrating with external services, continue to use an allowlist and do not forward injected headers that were not part of the original signature context. If you use the middleBrick CLI (middlebrick scan <url>) or the GitHub Action to add API security checks to your CI/CD pipeline, you can detect missing header allowlists and Hmac validation gaps before deployment. For continuous assurance, the Pro plan’s continuous monitoring can schedule regular scans and alert you if risk scores degrade due to new header-related findings.