HIGH http request smugglingbuffalohmac signatures

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.

Frequently Asked Questions

Why must Transfer-Encoding and Content-Length be included in the HMAC signature?
Including these headers in the HMAC ensures that the signature reflects how the request will be parsed. If a client smuggles a request by providing conflicting Transfer-Encoding and Content-Length, the signature will only validate if both are covered; otherwise an attacker can cause the server to interpret the body differently than the proxy.
Can Buffalo applications rely on the framework to prevent request smuggling when using HMAC signatures?
No, Buffalo does not automatically canonicalize or reject ambiguous header combinations. Developers must explicitly include the relevant headers in the signed payload and enforce consistent parsing rules to prevent smuggling.