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;
}
}
```

Please let me know if you need further clarification or assistance with testing 
the patch.

Best regards,

Mohamed Lemine Ahmed Jidou

Security Researcher | Zero-Day Research

AegisSec

Reply via email to