On Mon, Apr 13, 2026 at 12:42:30PM +0000, Mohamed Lemine Ahmed Jidou wrote:
> Hello,
> 
> During a source code review of httpd, I identified an RFC compliance issue in 
> how the HTTP header parser handles unrecognized or obfuscated 
> Transfer-Encoding values.
> 
> Instead of actively rejecting requests with malformed transfer encodings (as 
> mandated by RFC 7230), httpd silently ignores the header and falls back to 
> using the Content-Length header to determine the message body length. When 
> httpd is deployed behind a frontend reverse proxy (e.g., HAProxy, Nginx), 
> this semantic discrepancy allows an attacker to perform HTTP Request 
> Smuggling (TE.CL) attacks.
> 
> ### 1. Vulnerability Analysis & Source Code Reference
> 
> In usr.sbin/httpd/server_http.c, within the server_read_http function, the 
> parser handles the Transfer-Encoding header as follows:
> 
> ```c
> /* usr.sbin/httpd/server_http.c */
> if (strcasecmp("Transfer-Encoding", key) == 0 &&
> strcasecmp("chunked", value) == 0)
> desc->http_chunked = 1;
> 
> ```
> 
> The code strictly expects the value to be exactly "chunked". If an attacker 
> supplies an obfuscated value that a frontend proxy might normalize or accept 
> (e.g., Transfer-Encoding: chunked, identity or Transfer-Encoding: 
> chunked\\r), the strcasecmp condition evaluates to false.
> 
> Because there is no else block to handle unrecognized values for this 
> specific header, desc->http_chunked remains 0. The parser proceeds without 
> error and later uses clt->clt_toread (which was populated if a Content-Length 
> header was also provided in the request) to determine the body size.
> 
> ### 2. RFC 7230 Violation
> 
> This behavior is a direct violation of RFC 7230, Section 3.3.3, Paragraph 3, 
> which dictates how servers must handle requests containing both 
> Transfer-Encoding and Content-Length:
> 
> > "If a message is received with both a Transfer-Encoding and a 
> > Content-Length header field, the Transfer-Encoding overrides the 
> > Content-Length. [...] If a Transfer-Encoding header field is present in a 
> > request and the chunked transfer coding is not the final encoding, the 
> > message body length cannot be determined reliably; the server MUST respond 
> > with the 400 (Bad Request) status code and then close the connection."
> 
> By silently falling back to Content-Length rather than returning a 400 Bad 
> Request, httpd breaks the request boundary synchronization with upstream 
> proxies, creating a classic TE.CL smuggling vector.
> 
> ### 3. Steps to Reproduce (PoC)
> 
> To reproduce this behavior on a standalone OpenBSD httpd instance, send a 
> POST request with an invalid Transfer-Encoding and a valid Content-Length.
> 
> Run the following command against httpd:
> 
> ```bash
> printf "POST / HTTP/1.1\r\nHost: target.local\r\nContent-Length: 
> 6\r\nTransfer-Encoding: chunked, invalid\r\n\r\n0\r\n\r\nX" | nc <HTTPD_IP> 80
> 
> **Expected Result (per RFC 7230):** The server MUST reject the request 
> immediately with a `HTTP/1.0 400 Bad Request` and close the connection 
> because the transfer encoding is not strictly "chunked". **Actual Vulnerable 
> Result:** The server accepts the request and responds with a `200 OK`, `403 
> Forbidden`, or `404 Not Found` (depending on the configured routes). It 
> successfully processes the request using the `Content-Length: 6` to consume 
> the `0\\r\\n\\r\\nX` payload, completely ignoring the malformed 
> `Transfer-Encoding` header. ### 4. Security Impact In a typical reverse-proxy 
> architecture, the frontend proxy processes the request using the 
> `Transfer-Encoding` (interpreting the `0\\r\\n\\r\\n` as the end of the 
> chunked request), while `httpd` processes it using the `Content-Length`. This 
> leaves the trailing bytes (the smuggled request) in the backend TCP buffer. 
> The backend `httpd` will process this smuggled payload as the beginning of 
> the next user's HTTP request, leading to cache poisoning, WAF bypass, or 
> cross-user response hijacking. ### 5. Proposed Fix The parser should 
> explicitly reject the request if the `Transfer-Encoding` header is present 
> but its value is not supported. Suggested patch logic in `server_http.c`: c
> if (strcasecmp("Transfer-Encoding", key) == 0) {
> if (strcasecmp("chunked", value) == 0) {
> desc->http_chunked = 1;
> 
> } else {
> server_abort_http(clt, 400, "malformed transfer-encoding");
> return;
> }
> }
> ```
 
Playing devils advocate here. If your request makes it through the WAF /
reverse proxy then aren't those systems vulnerable to this and not httpd?

Now the handling of Transfer-Encoding and Content-Length needs to be
stricter in httpd, your simplistic fix is IMO not enough.

RFC9112 has:
A sender MUST NOT send a Content-Length header field in any message that
contains a Transfer-Encoding header field.

Your diff only covers possible headers like:
        Transfer-Encoding: gzip, chunked

Which is a start.

-- 
:wq Claudio

Reply via email to