CVE-2026-45372
Description
cpp-httplib is a C++11 single-file header-only cross platform HTTP/HTTPS library. Prior to 0.44.0, when cpp-httplib's server parses an incoming request, it applies percent-decoding to every header value except Location and Referer. The validity check (is_field_value) is run before decoding, so encoded %0D%0A passes the check and is then expanded to a literal \r\n byte pair inside the stored header value. This vulnerability is fixed in 0.44.0.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
cpp-httplib before 0.44.0 applies percent-decoding to HTTP header values after validation, enabling CRLF injection via encoded %0D%0A.
Vulnerability
In cpp-httplib versions prior to 0.44.0, the server's parse_header function applies percent-decoding to all header values except Location and Referer. The validity check (is_field_value) is performed on the raw (encoded) value, so an encoded CRLF sequence like %0D%0A passes validation and is then decoded into literal \r\n bytes stored in the header value. This violates RFC 9110 §5.5, which specifies that header field values are octets and should not be percent-decoded [1].
Exploitation
An attacker can send a crafted HTTP request containing a header with %0D%0A (e.g., X-Custom: a%0D%0AInjected: b). The server decodes this to inject a CRLF, allowing the attacker to inject additional headers or prematurely terminate the header section. No authentication or special network position is required; the attacker only needs to be able to send HTTP requests to the vulnerable server [1].
Impact
Successful exploitation enables CRLF injection, which can lead to HTTP response splitting, cache poisoning, session fixation, or cross-site scripting (XSS) if the server reflects injected headers in responses. The attacker can manipulate the server's response to inject arbitrary headers or body content, potentially compromising other users or systems that interact with the server [1].
Mitigation
The vulnerability is fixed in cpp-httplib version 0.44.0, released on 2026-05-29. Users should upgrade to this version immediately. No workarounds are documented; the fix ensures that percent-decoding is not applied to header values, aligning with RFC 9110 [1].
AI Insight generated on May 29, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
2<0.44.0+ 1 more
- (no CPE)range: <0.44.0
- (no CPE)range: <0.44.0
Patches
1fbb031ed8504Stop percent-decoding HTTP request header values
2 files changed · +121 −6
httplib.h+5 −6 modified@@ -5016,12 +5016,11 @@ inline bool parse_header(const char *beg, const char *end, T fn) { if (!detail::fields::is_field_value(val)) { return false; } - if (case_ignore::equal(key, "Location") || - case_ignore::equal(key, "Referer")) { - fn(key, val); - } else { - fn(key, decode_path_component(val)); - } + // RFC 9110 §5.5: header field values are opaque octets and MUST NOT be + // percent-decoded by the recipient. Applications that need to interpret a + // value as a URI component should call httplib::decode_uri_component() + // (or decode_path_component()) explicitly. + fn(key, val); return true; }
test/test.cc+116 −0 modified@@ -7441,6 +7441,122 @@ TEST(ServerRequestParsingTest, EmptyFieldValue) { EXPECT_EQ("HTTP/1.1 200 OK", out.substr(0, 15)); } +TEST(ServerRequestParsingTest, HeaderValueNotPercentDecoded) { + Server svr; + std::string x_custom; + std::string cookie; + std::string xff; + std::string x_unicode; + std::string x_iis; + + svr.Get("/check", [&](const Request &req, Response &res) { + x_custom = req.get_header_value("X-Custom"); + cookie = req.get_header_value("Cookie"); + xff = req.get_header_value("X-Forwarded-For"); + x_unicode = req.get_header_value("X-Unicode"); + x_iis = req.get_header_value("X-IIS"); + res.set_content("ok", "text/plain"); + }); + + thread t = thread([&] { svr.listen(HOST, PORT); }); + auto se = detail::scope_exit([&] { + svr.stop(); + t.join(); + ASSERT_FALSE(svr.is_running()); + }); + + svr.wait_until_ready(); + + const std::string req = "GET /check HTTP/1.1\r\n" + "Host: localhost\r\n" + "X-Custom: a%0D%0AInjected: b\r\n" + "Cookie: session%3Dvictim%3B%20admin%3Dyes\r\n" + "X-Forwarded-For: 1.2.3.4%2C5.6.7.8\r\n" + "X-Unicode: %E3%81%82\r\n" + "X-IIS: %u00E9\r\n" + "Connection: close\r\n" + "\r\n"; + + std::string res; + ASSERT_TRUE(send_request(5, req, &res)); + EXPECT_EQ("HTTP/1.1 200 OK", res.substr(0, 15)); + + // Every value must be returned verbatim (wire form), with no decoding. + EXPECT_EQ("a%0D%0AInjected: b", x_custom); + EXPECT_EQ("session%3Dvictim%3B%20admin%3Dyes", cookie); + EXPECT_EQ("1.2.3.4%2C5.6.7.8", xff); + EXPECT_EQ("%E3%81%82", x_unicode); + EXPECT_EQ("%u00E9", x_iis); +} + +// Applications that previously relied on automatic percent-decoding can +// reproduce the old behavior by explicitly calling decode_path_component() +// or, for RFC 3986 conformance, decode_uri_component(). +TEST(ServerRequestParsingTest, HeaderValueExplicitDecodingByApplication) { + Server svr; + std::string decoded; + + svr.Get("/check", [&](const Request &req, Response &res) { + decoded = decode_uri_component(req.get_header_value("X-Custom")); + res.set_content("ok", "text/plain"); + }); + + thread t = thread([&] { svr.listen(HOST, PORT); }); + auto se = detail::scope_exit([&] { + svr.stop(); + t.join(); + ASSERT_FALSE(svr.is_running()); + }); + + svr.wait_until_ready(); + + const std::string req = "GET /check HTTP/1.1\r\n" + "Host: localhost\r\n" + "X-Custom: hello%20world\r\n" + "Connection: close\r\n" + "\r\n"; + + std::string res; + ASSERT_TRUE(send_request(5, req, &res)); + EXPECT_EQ("HTTP/1.1 200 OK", res.substr(0, 15)); + EXPECT_EQ("hello world", decoded); +} + +// Regression test for #2033. Browsers send Referer values that include +// percent-encoded characters such as %0A inside the URL. Decoding the +// header value would either trip the post-decode CR/LF/NUL guard (the +// original bug, returning 400) or, after that guard was relaxed, silently +// store a literal LF — both unacceptable. The wire form must round-trip. +TEST(ServerRequestParsingTest, RefererWithPercentEncodedNewline) { + Server svr; + std::string referer; + + svr.Get("/check", [&](const Request &req, Response &res) { + referer = req.get_header_value("Referer"); + res.set_content("ok", "text/plain"); + }); + + thread t = thread([&] { svr.listen(HOST, PORT); }); + auto se = detail::scope_exit([&] { + svr.stop(); + t.join(); + ASSERT_FALSE(svr.is_running()); + }); + + svr.wait_until_ready(); + + const std::string req = "GET /check HTTP/1.1\r\n" + "Host: localhost\r\n" + "Referer: http://localhost:1111/?q=Hello%0A\r\n" + "Connection: close\r\n" + "\r\n"; + + std::string res; + ASSERT_TRUE(send_request(5, req, &res)); + EXPECT_EQ("HTTP/1.1 200 OK", res.substr(0, 15)); + EXPECT_EQ("http://localhost:1111/?q=Hello%0A", referer); +} + TEST(ServerStopTest, StopServerWithChunkedTransmission) { Server svr;
Vulnerability mechanics
Root cause
"Missing re-validation after percent-decoding of HTTP header values allows encoded CR/LF sequences to become literal control bytes in stored header values."
Attack vector
An unauthenticated remote attacker sends an HTTP request to the server with a crafted header value containing percent-encoded control characters such as `%0D%0A`. Because the validity check (`is_field_value`) runs before percent-decoding, the encoded form passes validation and is then expanded into literal `\r\n` bytes inside the stored header value [ref_id=1]. This enables HTTP response splitting, log injection, and proxy smuggling when the decoded value is later reflected in response headers, logged, or forwarded by the bundled client [ref_id=1]. The attack also causes Cookie and X-Forwarded-For boundary confusion because WAFs see the wire form while the application sees the decoded form [ref_id=1].
Affected code
The vulnerability resides in `parse_header()` inside `httplib.h`. The function applies `decode_path_component()` to every header value except `Location` and `Referer` after the `is_field_value()` validation has passed, so encoded sequences like `%0D%0A` are accepted by the check and then expanded into literal CR/LF bytes in the stored value. The patch removes the percent-decoding call entirely and eliminates the `Location`/`Referer` special case.
What the fix does
The patch removes the `decode_path_component(val)` call from `parse_header()` and drops the `Location`/`Referer` special case that was a workaround for the same auto-decode misbehavior [patch_id=3107118]. Per RFC 9110 §5.5, header field values are opaque octets and must not be percent-decoded by the recipient. Applications that need URI semantics can call `decode_uri_component()` or `decode_path_component()` explicitly on the retrieved value. Regression tests verify that wire-form sequences like `%0D%0A`, `%3D`, `%2C`, and `%0A` in Referer are stored verbatim without decoding [patch_id=3107118].
Preconditions
- networkThe attacker must be able to send arbitrary HTTP requests to a cpp-httplib server that parses header values and later reflects, logs, or forwards them.
- authNo authentication is required; the vulnerability is triggered during request parsing before any application logic runs.
Generated on May 29, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
1News mentions
0No linked articles in our index yet.