Http Request Smuggling in Buffalo with Hmac Signatures
Http Request Smuggling in Buffalo with Hmac Signatures
HTTP request smuggling arises when an intermediary (such as a load balancer or reverse proxy) and the backend server disagree on how to parse the boundaries of an HTTP request. In the Buffalo web framework, this risk can be amplified when HMAC signatures are used to authenticate requests but the signature is computed over a subset of the message while the server parses a different effective body. If a client sends a request with Transfer-Encoding and Content-Length headers that conflict, and the HMAC is calculated only over the path and selected headers (omitting the smuggling-relevant headers), an attacker can craft a request that is interpreted differently by the proxy versus the server. This can cause the server to treat a smuggled request as part of the next transaction, bypassing intended access controls or allowing injection of malicious payloads.
Consider a Buffalo app that validates an X-API-Signature header computed over the request body and a static API key. If the signature does not cover the presence or value of Transfer-Encoding: chunked, an attacker can send a request with both Transfer-Encoding: chunked and Content-Length. The proxy may parse using Transfer-Encoding, while the backend server (or an intermediate handler) parses using Content-Length. Because the HMAC was verified before routing, the application may forward the request with the smuggled body to an authenticated handler that trusts the signature, leading to request smuggling. This pattern is particularly relevant when the framework’s middleware stack does not canonicalize headers before signature computation. Real-world attacks include request splitting and request injection, which can poison caches, bypass authentication, or enable cross-user data access.
In Buffalo, HMAC-based signatures are typically computed in a before action or in a plug that verifies X-API-Signature. If the plug hashes only selected headers and the body but ignores the canonicalization of Transfer-Encoding and Content-Length, the signature may still validate while the request is ambiguous to the server. Because Buffalo applications often rely on standard HTTP libraries for parsing, the framework itself does not resolve header conflicts; it is up to the developer to ensure that the signed representation matches how the server will interpret the request. This discrepancy is where the smuggling surface exists: the signature covers what the developer thinks is the request, but the server may parse a different request due to header manipulation.
Hmac Signatures-Specific Remediation in Buffalo
To mitigate request smuggling when using HMAC signatures in Buffalo, you must ensure that the signature covers all headers and the body that participate in request parsing, and that you canonicalize those inputs consistently between client and server. In practice, this means including Transfer-Encoding and Content-Length (if both are present) in the signed payload, or rejecting requests that contain both. Below are concrete code examples demonstrating how to compute and verify HMAC signatures in a way that reduces smuggling risk.
Client-side signing example
Sign a canonical string that includes method, path, sorted headers of interest, and body. This canonical string is then used to produce the X-API-Signature header.
import Crypto.Hmac
import Crypto.Hash (SHA256(..))
import Data.ByteString (ByteString)
import qualified Data.ByteString.Char8 as BSC
import Data.ByteString.Base16 (encode)
canonicalRequest :: ByteString -> ByteString -> [(ByteString, ByteString)] -> ByteString -> ByteString
canonicalRequest method path headers body =
BSC.unlines
[ method
, path
, BSC.unlines (map (\(k, v) -> k <> ":" <> v) (sort headers))
, body
]
signPayload :: ByteString -> ByteString -> ByteString -> ByteString
signPayload apiKey method path headers body =
let message = canonicalRequest method path headers body
hmac = hmac apiKey message :: ByteString
in encode hmac
-- Example usage:
-- signPayload "my-secret" "POST" "/api/charge" [(Transfer-Encoding, chunked),(Content-Length, "123")] "{...}"
Server-side verification in a Buffalo plug
In a Buffalo application, implement a plug that reconstructs the canonical string using the incoming request’s method, path, selected headers, and body, then compares the computed HMAC to the X-API-Signature header. Ensure that the set of headers used for signing matches the client’s canonicalization, and consider rejecting requests that include both Transfer-Encoding and Content-Length to avoid ambiguity.
import Web.Controller.Prelude
import Crypto.Hmac
import Crypto.Hash (SHA256(..))
import Data.ByteString (ByteString)
import qualified Data.ByteString.Char8 as BSC
import Data.ByteString.Base16 (decodeLenient)
verifySignature :: ByteString -> ActionM ()
verifySignature apiKey = do
method <- requestMethodParam
path <- parseUrlParam "path" -- simplified; use actual path
let headers = filter (\(k,_) -> k `elem` ["Transfer-Encoding","Content-Length","Content-Type"]) (requestHeaders)
body <- readRequestBody
let message = BSC.unlines
[ method
, path
, BSC.unlines (map (\(k, v) -> k <> ":" <> v) headers)
, fromMaybe "" body
]
let receivedSig = header "X-API-Signature"
case receivedSig of
Nothing -> invalidArgs ["missing signature"]
Just sigB64 -> do
let computed = computeHmac apiKey message
unless (constantTimeEq computed (decodeLenient sigB64)) $ do
setResponseStatus 401 "Unauthorized"
json $ object ["error" .= ("invalid signature" :: Text)]
computeHmac :: ByteString -> ByteString -> ByteString
computeHmac key msg =
let hmac = hmac key msg :: ByteString
in hmac
constantTimeEq :: ByteString -> ByteString -> Bool
constantTimeEq a b = length a == length b && (==) `on` a b
Additionally, prefer to reject or normalize requests that include both Transfer-Encoding and Content-Length before computing the signature. This prevents a client from smuggling a request body that the server may interpret differently. By including these headers in the signed canonical string, any discrepancy between proxy and server parsing will cause the signature to fail, stopping the request early.