Request Smuggling with Hmac Signatures
How Request Smuggling Manifests in Hmac Signatures
When APIs use HMAC signatures for request integrity, they typically compute the signature over a canonical representation of the request—often including headers, method, path, and body. Request smuggling emerges when there's a discrepancy between how the HMAC is computed and how the backend ultimately processes the request, allowing an attacker to craft a request that passes signature verification but is interpreted differently by the server.
Header Parsing Ambiguity: Many HTTP parsers normalize headers (e.g., converting X-Header: value1, value2 into multiple X-Header entries). If the HMAC is computed over the raw header string but the backend uses normalized headers for authorization, an attacker can inject a duplicate header. For example, a signed Authorization: HMAC ... header could be followed by X-Forwarded-For: evil. If the HMAC validation uses the raw header block but the application logic later reads the normalized X-Forwarded-For (which now includes the attacker's value), the attacker may bypass IP-based restrictions or poison logs.
Transfer-Encoding/Content-Length Conflicts: HMAC signatures often include the Content-Length header. An attacker could send a request with both Transfer-Encoding: chunked and Content-Length. Some frontends (like load balancers) may process the chunked body, while the backend uses Content-Length. If the HMAC was computed over the Content-Length value but the backend reads a different number of bytes due to chunked encoding, the body used in application logic differs from the signed body, potentially allowing parameter pollution or bypassing input validation that was part of the signature.
Path Normalization: If the HMAC covers the request path (e.g., /api/v1/resource) but the backend normalizes it (resolving .., double slashes, or URL-encoded characters), an attacker can send a path like /api/v1/../admin. The HMAC validates against the literal path, but the backend routes to /admin, potentially accessing unauthorized endpoints that were not covered by the signature's scope.
Real-World Example (Node.js/Express Vulnerable Pattern):
const crypto = require('crypto');
app.use((req, res, next) => {
const signature = req.headers['x-signature'];
const body = JSON.stringify(req.body); // Body parsed by express.json()
const expected = crypto.createHmac('sha256', secret)
.update(req.method + req.path + body)
.digest('hex');
if (signature !== expected) return res.status(401).send('Invalid signature');
next();
});Here, the HMAC is computed after Express has already parsed the JSON body and normalized the path. An attacker can send a request with a raw body that parses differently (e.g., duplicate keys, type confusion) or a path with %2e%2e sequences that Express normalizes, causing the signed data to differ from the data used in business logic.
Hmac Signatures-Specific Detection
Detecting request smuggling in HMAC-signed APIs requires testing for canonicalization mismatches between signature computation and request processing. middleBrick's Authentication and Input Validation checks are designed to probe these specific weaknesses during its 5–15 second black-box scan.
Test Cases:
- Header Duplication: Send a request with duplicate headers (e.g., two
AuthorizationorContent-Typeheaders). Observe if the backend uses a different header value than the one signed. Also test with comma-separated vs. repeated headers. - Path Normalization: Submit paths with
.., double slashes (//), URL-encoded characters (%2e%2e), or mixed case. Check if the HMAC validation fails but the request still reaches the endpoint, indicating the backend normalized the path after validation. - Transfer-Encoding/Content-Length: Send requests with both headers present but conflicting values. Use chunked encoding with a
Content-Lengththat doesn't match the chunked body size. If the HMAC validation usesContent-Lengthbut the backend processes the chunked body, the signed length differs from the actual processed body. - Body Parsing Ambiguity: For JSON APIs, send bodies with duplicate keys (
{"id":1,"id":2}), non-canonical whitespace, or different number formats (1vs1.0). The HMAC may be computed over one representation, but the parser might retain the last value or a different type.
Using middleBrick: Submit your API endpoint to the middleBrick web dashboard or use the CLI:
middlebrick scan https://api.example.com/endpointThe scan runs 12 parallel security checks, including specific probes for HMAC canonicalization issues. The report will flag any findings under the Authentication or Input Validation categories, with a severity rating (Critical/High/Medium/Low) and remediation steps. The OpenAPI/Swagger analysis cross-references your spec's defined parameters with runtime behavior to detect mismatches that could enable smuggling.
Hmac Signatures-Specific Remediation
Remediation centers on ensuring a single, immutable canonical representation of the request is used both for HMAC computation and for backend processing. Never rely on the framework's parsed objects if they may normalize data differently than the signature verification step.
1. Canonicalize Before Signing and Verifying: Define a strict canonical form (e.g., HTTP/1.1 wire format) and use it consistently. In Node.js, use a library like canonical-json for bodies and normalize headers explicitly:
const canonicalize = require('canonical-json');
const httpSignature = require('http-signature'); // Or implement manually
function verifyRequest(req) {
// Reconstruct the exact string that was signed
const signedHeaders = req.headers['x-signed-headers']?.split(' ') || ['(request-target)', 'host', 'date'];
const canonical = {
method: req.method,
path: req.path, // Use raw, unnormalized path from req.url
headers: signedHeaders.map(name => {
const value = req.rawHeaders.find(([key]) => key.toLowerCase() === name.toLowerCase())?.[1] || '';
return `${name}: ${value.trim()}`;
}).join('\n'),
body: req.body ? canonicalize(req.body) : ''
};
const signature = req.headers['signature'];
const expected = crypto.createHmac('sha256', secret)
.update(Object.values(canonical).join('\n'))
.digest('base64');
// Verify signature matches
if (!httpSignature.verifySignature(canonical, signature, secret)) {
throw new Error('Invalid signature');
}
// After verification, use the canonical values for routing/authorization
req.canonicalPath = canonical.path;
req.canonicalBody = canonical.body;
}2. Reject Ambiguous Requests: If the request contains both Transfer-Encoding and Content-Length, reject it with 400 Bad Request before any parsing. Similarly, reject requests with duplicate headers that are part of the signature.
3. Use a Single Parser: Ensure the same library (or raw parsing) is used for both signature verification and business logic. In Express, access the raw request (req.rawHeaders, req.url) instead of normalized properties like req.path during verification. After verification, map to the normalized values for application use.
4. Sign Everything That Matters: Include all headers that influence authorization or routing in the signature. Use the Digest header for body integrity (RFC 3230) and include it in the HMAC. This prevents body substitution attacks.
5. Framework-Specific Guidance:
- Spring (Java): Use
OncePerRequestFilterto read the rawHttpServletRequestviarequest.getInputStream()andrequest.getHeaderNames()before any filters that might modify the request. Cache the canonical bytes and reuse them. - Django (Python): Access
request.METAfor raw headers andrequest.body(bytes) before any middleware modifiesrequest.POST. Normalize the body (e.g., sort JSON keys) consistently.
Integrate these checks into your CI/CD pipeline using the middleBrick GitHub Action to catch regressions. The action can fail a build if the security score drops below a threshold, ensuring HMAC canonicalization issues are caught early.
FAQ
- How does request smuggling bypass HMAC signatures? The HMAC validates a specific representation of the request (e.g., raw headers, path, body). If the backend processes a different representation due to parsing ambiguities (e.g., header normalization, path traversal, chunked encoding), an attacker can craft a request that passes HMAC verification but is interpreted differently, potentially accessing unauthorized resources or corrupting data.
- Can middleBrick detect this without credentials? Yes. middleBrick performs black-box testing by sending crafted requests with header duplications, path traversals, and conflicting
Transfer-Encoding/Content-Lengthheaders. It observes the responses and behavior to infer canonicalization mismatches. The Authentication and Input Validation checks include specific probes for HMAC-related smuggling patterns, providing findings with severity and remediation guidance.