Http Request Smuggling in Fiber with Hmac Signatures
Http Request Smuggling in Fiber with Hmac Signatures — how this specific combination creates or exposes the vulnerability
HTTP Request Smuggling arises when an HTTP request is parsed differently by a frontend proxy/load balancer and a backend application, allowing an attacker to smuggle a crafted request from one interpretation to the other. In Fiber, a high-performance Go web framework, this can occur when HMAC signatures are applied only to the request body (or only to selected headers) while the frontend and backend have inconsistent rules about what constitutes the canonical request for signing.
Consider a setup where a frontend (e.g., CDN or API gateway) terminates TLS and forwards requests to Fiber. If the frontend signs and validates requests using a canonical string that includes, for example, the method, path, and body—but the Fiber app signs using a different subset (such as method and body only, omitting path normalization)—an attacker can exploit this mismatch. A request like POST /api/v1/users HTTP/1.1 with a valid HMAC for the body might be forwarded by the frontend as POST /api/v1/users HTTP/1.0 or with an added Transfer-Encoding: chunked header. If Fiber validates the HMAC using the same canonical scope as the frontend, the signature may still verify because the attacker ensures the canonical string aligns. However, if the frontend strips or alters headers before computing the HMAC, the backend may compute a different canonical string and either reject the signature (causing false positives) — or worse, accept a smuggled request where the frontend intended to drop it. This becomes critical when HMAC logic is implemented inconsistently across the stack, enabling BOLA/IDOR-like smuggling where one user’s request is interpreted as another’s.
In practice, if the Fiber app uses a body-only HMAC scope but the frontend includes the request path and a normalized header list, an attacker can smuggle an extra request by leveraging header folding or chunked encoding differences. For example, an attacker sends two requests in one TCP stream:
POST /api/v1/transfers HTTP/1.1
Content-Type: application/json
Content-Length: 45
X-API-Key: alice
X-Signature: VALID_HMAC_FOR_BODY_ONLY
{"amount":100,"to":"attacker"}
GET /api/v1/admin/deleteAll HTTP/1.1
Host: api.example.com
If the frontend parses this as two requests and forwards them with the same canonical rules, but Fiber parses the second request as part of the same smuggled stream, the second request may bypass authorization because its HMAC was not validated (or validated against a different canonical scope). The vulnerability is not in Fiber itself but in how the HMAC canonicalization scope is defined and aligned between the proxy and the application. This misalignment maps to OWASP API Top 10 API1:2023 Broken Object Level Authorization when smuggling leads to privilege escalation or unauthorized actions.
Hmac Signatures-Specific Remediation in Fiber — concrete code fixes
To mitigate smuggling when using HMAC signatures in Fiber, ensure the canonical request used for signing and verification is identical across the frontend and the Fiber app, and include headers that prevent request splitting and ambiguity. Below are concrete Fiber examples that demonstrate a robust approach.
1. Canonical request including method, path, selected headers, and body
Define a deterministic string to sign that both sides agree on. Include the HTTP method, the path without duplicate slashes or unnecessary encodings, a sorted subset of headers (e.g., content-type and x-timestamp), and the request body.
const crypto = require('crypto');
function buildCanonicalString(req) {
// Normalize path: remove double slashes, ensure lowercase method
const method = req.method.toUpperCase();
const path = req.path.replace(/\/+/g, '/');
// Select headers important for signing; keep order deterministic
const headers = ['content-type', 'x-timestamp']
.map(h => h.toLowerCase())
.filter(h => req.get(h) !== undefined)
.map(h => `${h}:${req.get(h).trim().toLowerCase()}`)
.join('\n');
const body = req.body || '';
return `${method}\n${path}\n${headers}\n\n${body}`;
}
function signPayload(secret, canonical) {
return crypto.createHmac('sha256', secret).update(canonical).digest('hex');
}
// Example usage in Fiber handler
const express = require('express');
const app = express();
app.use(express.json());
app.post('/api/action', (req, res) => {
const expected = req.get('x-signature');
const canonical = buildCanonicalString(req);
const computed = signPayload(process.env.HMAC_SECRET, canonical);
if (!crypto.timingSafeEqual(Buffer.from(expected || ''), Buffer.from(computed))) {
return res.status(401).json({ error: 'invalid signature' });
}
// proceed with business logic
res.json({ ok: true });
});
2. Proxy-to-app agreement on canonicalization and rejection of suspicious headers
Ensure the frontend and Fiber agree to exclude headers that can be used for smuggling, such as Transfer-Encoding, and to reject requests containing both Content-Length and Transfer-Encoding. The Fiber app should normalize the request path and reject malformed requests before HMAC verification.
// Middleware to enforce safe headers and path normalization before signing/verification
app.post('/api/action', (req, res, next) => {
// Reject requests with ambiguous transfer encodings
const te = req.get('transfer-encoding');
const cl = req.get('content-length');
if (te && cl) {
return res.status(400).json({ error: 'ambiguous transfer encoding' });
}
// Normalize path
req.path = req.path.replace(/\/+/g, '/');
next();
}, (req, res) => {
const expected = req.get('x-signature');
const canonical = buildCanonicalString(req);
const computed = signPayload(process.env.HMAC_SECRET, canonical);
if (!expected || !crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(computed))) {
return res.status(401).json({ error: 'invalid signature' });
}
res.json({ ok: true });
});
3. Include a nonce or timestamp to prevent replay and clarify scope
Adding a short-lived timestamp or nonce as a signed header reduces the window for smuggling attempts and ensures each canonical string is unique. The frontend must include this header, and Fiber must validate it within an acceptable time drift window.
// Frontend would add:
// const timestamp = Date.now().toString();
// const canonical = buildCanonicalString({ method, path, headers: [...], body, timestamp });
// req.headers['x-timestamp'] = timestamp;
// req.headers['x-signature'] = signPayload(secret, canonical);
// Fiber side: extend buildCanonicalString to require x-timestamp and check freshness
function buildCanonicalString(req) {
const timestamp = req.get('x-timestamp');
if (!timestamp || Math.abs(Date.now() - parseInt(timestamp, 10)) > 30000) {
throw new Error('missing or stale timestamp');
}
const method = req.method.toUpperCase();
const path = req.path.replace(/\/+/g, '/');
const headers = ['content-type', 'x-timestamp'].map(h => h.toLowerCase()).filter(h => req.get(h)).map(h => `${h}:${req.get(h).trim().toLowerCase()}`).join('\n');
const body = req.body || '';
return `${method}\n${path}\n${headers}\n\n${body}`;
}
By standardizing the canonical request and hardening header handling, you reduce the conditions that enable HTTP Request Smuggling when HMAC signatures are used.