From bb756d7f825cba4574fcd19f231761d3f868f93b Mon Sep 17 00:00:00 2001 From: rofl0r Date: Tue, 23 Jun 2026 22:11:44 +0000 Subject: [PATCH 1/5] sanitize transfer-encoding and reject malformed "chunked" addressing #616 (yet another instance of request smuggling) --- src/reqs.c | 56 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 51 insertions(+), 5 deletions(-) diff --git a/src/reqs.c b/src/reqs.c index 0998324d..55e294ae 100644 --- a/src/reqs.c +++ b/src/reqs.c @@ -826,11 +826,50 @@ static long get_content_length (pseudomap *hashofheaders) return content_length; } -static int is_chunked_transfer (pseudomap *hashofheaders) -{ +/* "stomps" transfer-encoding string, removing superfluous whitespace. + return 1 if "chunked was found at the correct position, either at the + beginning of the value, or at the end following a comma and optional + whitespace. + if chunked was found at an incorrect position, -1, else 0. */ +static int +check_chunked_and_sanitize_transfer_encoding(pseudomap *hashofheaders) { char *data; + int was_comma = 0, ret = 0, c; + char *ins, *p, *chunked = 0; data = pseudomap_find (hashofheaders, "transfer-encoding"); - return data ? !strcasecmp (data, "chunked") : 0; + if (!data) return 0; + ins = p = data; + while (*p) { + c = *(p++); + switch (c) { + case ',': + if (chunked) ret = -1; + if (!was_comma) *(ins++) = c; + was_comma = 1; + break; + case '\t': case ' ': + if (was_comma) { + if (was_comma == 1) *(ins++) = ' '; + ++was_comma; + } else was_comma = 0; + break; + case 'C': case 'c': + if (!strncasecmp(p, "hunked", 6)) { + if (chunked || !(was_comma || ins == data)) + ret = -1; + chunked = p - 1; + } + /* fall-through */ + default: + was_comma = 0; + *(ins++) = c; + } + } + *ins = 0; + if (ret == -1) return ret; + /* after sanitization, if chunked was found, it needs to be at the end */ + if (chunked && chunked[7] == 0) return 1; + return 0; } /* @@ -921,8 +960,15 @@ process_client_headers (struct conn_s *connptr, pseudomap *hashofheaders) */ connptr->content_length.client = get_content_length (hashofheaders); - /* Check whether client sends chunked data. */ - if (is_chunked_transfer (hashofheaders)) { + ret = check_chunked_and_sanitize_transfer_encoding(hashofheaders); + if (ret == -1) { + /* bad transfer-encoding: RFC 9112 6.1 */ + indicate_http_error (connptr, 400, + "Bad Request", + NULL); + goto PULL_CLIENT_DATA; + } else if (ret == 1) { + /* well-formated "chunked" transfer-encoding */ if (connptr->content_length.client != -1) /* request smuggling, see GH issue #609 */ pseudomap_remove (hashofheaders, "content-length"); From 2031ed1fac8e600e88f62f0c361d5ec175c31235 Mon Sep 17 00:00:00 2001 From: rofl0r Date: Tue, 23 Jun 2026 22:32:34 +0000 Subject: [PATCH 2/5] address review --- src/reqs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/reqs.c b/src/reqs.c index 55e294ae..bb50dc5a 100644 --- a/src/reqs.c +++ b/src/reqs.c @@ -843,7 +843,7 @@ check_chunked_and_sanitize_transfer_encoding(pseudomap *hashofheaders) { c = *(p++); switch (c) { case ',': - if (chunked) ret = -1; + if (was_comma || chunked) ret = -1; if (!was_comma) *(ins++) = c; was_comma = 1; break; @@ -857,7 +857,7 @@ check_chunked_and_sanitize_transfer_encoding(pseudomap *hashofheaders) { if (!strncasecmp(p, "hunked", 6)) { if (chunked || !(was_comma || ins == data)) ret = -1; - chunked = p - 1; + chunked = ins; } /* fall-through */ default: From d290199647f9deaa692170fc631822bec44acea9 Mon Sep 17 00:00:00 2001 From: rofl0r Date: Tue, 23 Jun 2026 22:35:23 +0000 Subject: [PATCH 3/5] also reject comma at start --- src/reqs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reqs.c b/src/reqs.c index bb50dc5a..bc830c8e 100644 --- a/src/reqs.c +++ b/src/reqs.c @@ -843,7 +843,7 @@ check_chunked_and_sanitize_transfer_encoding(pseudomap *hashofheaders) { c = *(p++); switch (c) { case ',': - if (was_comma || chunked) ret = -1; + if (was_comma || chunked || ins == data) ret = -1; if (!was_comma) *(ins++) = c; was_comma = 1; break; From 76ddd9ebf3bf21adf3c52f6951dc20faafa94c9d Mon Sep 17 00:00:00 2001 From: rofl0r Date: Tue, 23 Jun 2026 22:45:13 +0000 Subject: [PATCH 4/5] more review changes --- src/reqs.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/reqs.c b/src/reqs.c index bc830c8e..e04aa3c0 100644 --- a/src/reqs.c +++ b/src/reqs.c @@ -826,11 +826,10 @@ static long get_content_length (pseudomap *hashofheaders) return content_length; } -/* "stomps" transfer-encoding string, removing superfluous whitespace. - return 1 if "chunked was found at the correct position, either at the - beginning of the value, or at the end following a comma and optional +/* In-place sanitize the Transfer-Encoding value by removing superfluous whitespace. - if chunked was found at an incorrect position, -1, else 0. */ + Returns 1 if a valid trailing "chunked" transfer-coding is present. + Returns -1 if "chunked" is present but malformed / not final; otherwise 0. */ static int check_chunked_and_sanitize_transfer_encoding(pseudomap *hashofheaders) { char *data; @@ -867,8 +866,8 @@ check_chunked_and_sanitize_transfer_encoding(pseudomap *hashofheaders) { } *ins = 0; if (ret == -1) return ret; - /* after sanitization, if chunked was found, it needs to be at the end */ - if (chunked && chunked[7] == 0) return 1; + /* after sanitization, if chunked was found, it needs to be the final coding */ + if (chunked && (chunked[7] == 0 || chunked[7] == ';')) return 1; return 0; } @@ -968,7 +967,7 @@ process_client_headers (struct conn_s *connptr, pseudomap *hashofheaders) NULL); goto PULL_CLIENT_DATA; } else if (ret == 1) { - /* well-formated "chunked" transfer-encoding */ + /* well-formatted "chunked" transfer-encoding */ if (connptr->content_length.client != -1) /* request smuggling, see GH issue #609 */ pseudomap_remove (hashofheaders, "content-length"); From d95fc6dd16e61e1b9d3ab638b6dd97f7efd4aa49 Mon Sep 17 00:00:00 2001 From: rofl0r Date: Tue, 23 Jun 2026 22:51:54 +0000 Subject: [PATCH 5/5] prevent duplicate transfer-encoding header --- src/reqs.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/reqs.c b/src/reqs.c index e04aa3c0..0351de1c 100644 --- a/src/reqs.c +++ b/src/reqs.c @@ -619,6 +619,14 @@ static int add_xtinyproxy_header (struct conn_s *connptr) } #endif /* XTINYPROXY */ +static int +check_duplicate_header (pseudomap *hashofheaders, char *header, const char* kw) +{ + return (!strcasecmp(header, kw) && + pseudomap_find (hashofheaders, kw)); +} + + /* * Take a complete header line and break it apart (into a key and the data.) * Now insert this information into the hashmap for the connection so it @@ -643,9 +651,9 @@ add_header_to_connection (pseudomap *hashofheaders, char *header, size_t len) /* Calculate the new length of just the data */ len -= sep - header - 1; - /* prevent multiple content-length headers from being inserted */ - if (!strcasecmp(header, "content-length") && - pseudomap_find (hashofheaders, "content-length")) + /* prevent multiple CL/TE headers from being inserted */ + if (check_duplicate_header(hashofheaders, header, "content-length") || + check_duplicate_header(hashofheaders, header, "transfer-encoding")) return 0; return pseudomap_append (hashofheaders, header, sep);