Summary
An off-by-one error in Mongoose's HTTP version detection causes the server to never recognize HTTP/1.0 requests. The check hm.proto.len > 8 at line 2781 is always false because mg_http_parse() enforces proto.len == 8 for valid requests. As a result, Transfer-Encoding: chunked is accepted for HTTP/1.0 requests, violating RFC 7230 (chunked was introduced in HTTP/1.1). When Mongoose sits behind a proxy operating in HTTP/1.0 mode, this enables CL.TE request smuggling — the proxy uses Content-Length while Mongoose processes chunked encoding, allowing an attacker to inject arbitrary requests.
Severity
- CVSS 3.1: 9.1 (Critical) —
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N
- CWE: CWE-444 (Inconsistent Interpretation of HTTP Requests — "HTTP Request Smuggling")
Affected Version
Vulnerability Details
Root Cause
The HTTP version detection at line 2780–2781:
is_http_1_0 =
hm.proto.len > 8 && mg_ncasecmp(hm.proto.buf, "HTTP/1.0", 8) == 0;
The condition hm.proto.len > 8 requires the protocol string to be longer than 8 characters. However, mg_http_parse() enforces hm->proto.len == 8 for all valid HTTP requests (line 2032):
if (!is_response && hm->proto.len > 0 &&
(!version_prefix_valid || hm->proto.len != 8 || // <-- enforces len == 8
(hm->proto.buf[5] < '0' || hm->proto.buf[5] > '9') ||
(hm->proto.buf[6] != '.') ||
(hm->proto.buf[7] < '0' || hm->proto.buf[7] > '9'))) {
return -1;
}
Since 8 > 8 is always false, is_http_1_0 is never true. The guard at line 2783 that prevents chunked TE for HTTP/1.0 never activates:
// HTTP/1.0 does not use "Transfer-Encoding: chunked"
if (!is_http_1_0 && // <-- always true
(te = mg_http_get_header(&hm, "Transfer-Encoding")) != NULL) {
if (mg_strcasecmp(*te, mg_str("chunked")) == 0) {
is_chunked = true; // <-- always reached
}
}
Smuggling Scenario
When Mongoose sits behind a reverse proxy operating in HTTP/1.0 mode:
- Proxy (HTTP/1.0): Sees
Content-Length: 0 and Transfer-Encoding: chunked. Since HTTP/1.0 does not define chunked TE, the proxy ignores the Transfer-Encoding header and uses Content-Length: 0. It considers the request body empty and forwards everything after the headers as the start of the next request.
- Mongoose (backend): Due to the off-by-one bug, does not detect HTTP/1.0 and processes
Transfer-Encoding: chunked. It reads the chunked body, then treats any data after the 0\r\n\r\n chunk terminator as a new pipelined request.
- Result: The attacker's smuggled request (embedded in the chunked body) is processed by Mongoose as a separate request, but the proxy attributes it to the next legitimate connection — a classic CL.TE desync.
Proof of Concept
// Minimal C proof that HTTP/1.0 detection is broken in Mongoose v7.21
// Build: gcc -DMG_ENABLE_LINES -I<mongoose_src> poc.c -o poc
// Run: ./poc
#include "mongoose.c"
#include <stdio.h>
int main(void) {
struct mg_http_message hm;
const char *req10 = "GET / HTTP/1.0\r\nHost: test\r\nTransfer-Encoding: chunked\r\n\r\n";
const char *req11 = "GET / HTTP/1.1\r\nHost: test\r\nTransfer-Encoding: chunked\r\n\r\n";
printf("Mongoose v%s HTTP/1.0 off-by-one PoC\n\n", MG_VERSION);
mg_http_parse(req10, strlen(req10), &hm);
int bug_1_0 = hm.proto.len > 8 && mg_ncasecmp(hm.proto.buf, "HTTP/1.0", 8) == 0;
printf("HTTP/1.0: proto.len=%zu, is_http_1_0=%d (SHOULD BE 1)\n",
hm.proto.len, bug_1_0);
mg_http_parse(req11, strlen(req11), &hm);
int bug_1_1 = hm.proto.len > 8 && mg_ncasecmp(hm.proto.buf, "HTTP/1.1", 8) == 0;
printf("HTTP/1.1: proto.len=%zu, is_http_1_1=%d (SHOULD BE 0 — not used)\n",
hm.proto.len, bug_1_1);
printf("\nBUG: hm.proto.len > 8 is ALWAYS false (proto.len is always 8)\n");
printf("FIX: Change > 8 to >= 8 at mongoose.c:2781\n");
printf("\nIMPACT: Chunked Transfer-Encoding accepted for HTTP/1.0 requests.\n");
printf(" Enables CL.TE request smuggling behind HTTP/1.0 proxies.\n");
return bug_1_0 == 0 ? 0 : 1;
}
Steps to Reproduce
gcc -DMG_ENABLE_LINES -I<mongoose_src> poc.c -o poc
./poc
Expected (correct) Output
HTTP/1.0: proto.len=8, is_http_1_0=1 (SHOULD BE 1)
Actual Output (vulnerable)
Mongoose v7.21 HTTP/1.0 off-by-one PoC
HTTP/1.0: proto.len=8, is_http_1_0=0 (SHOULD BE 1)
HTTP/1.1: proto.len=8, is_http_1_1=0 (SHOULD BE 0 — not used)
BUG: hm.proto.len > 8 is ALWAYS false (proto.len is always 8)
FIX: Change > 8 to >= 8 at mongoose.c:2781
IMPACT: Chunked Transfer-Encoding accepted for HTTP/1.0 requests.
Enables CL.TE request smuggling behind HTTP/1.0 proxies.
Output Analysis
| Input |
proto.len |
> 8 (buggy) |
>= 8 (fixed) |
Result |
GET / HTTP/1.0\r\n... |
8 |
false |
true |
HTTP/1.0 NOT detected |
GET / HTTP/1.1\r\n... |
8 |
false |
true |
(not checked for 1.1) |
The bug is unconditional — every HTTP/1.0 request is misidentified.
Impact
- Confidentiality: High — smuggled requests access resources intended for other sessions (cached responses, in-flight authenticated requests).
- Integrity: High — smuggled requests can modify state on behalf of other users (CSRF-equivalent without same-origin requirements).
- Availability: None.
- Attack vector: Network. Requires Mongoose behind a proxy operating in HTTP/1.0 mode. Common configurations include stunnel → Mongoose, HAProxy with
option http-server-close, legacy load balancers, and embedded gateway appliances using HTTP/1.0 upstream. Mongoose is widely deployed in IoT/embedded devices, which are frequently accessed through reverse proxies, VPN appliances, or cloud gateways that may use HTTP/1.0 for backend connections.
Suggested Fix
Change > to >= at line 2781:
// Before (buggy):
is_http_1_0 =
hm.proto.len > 8 && mg_ncasecmp(hm.proto.buf, "HTTP/1.0", 8) == 0;
// After (fixed):
is_http_1_0 =
hm.proto.len >= 8 && mg_ncasecmp(hm.proto.buf, "HTTP/1.0", 8) == 0;
Patched In
Fixed in the 2026-06-23 Mongoose release (SBOM 7.21_1ddf6c2cbd_5ab08203 or later).
References
Summary
An off-by-one error in Mongoose's HTTP version detection causes the server to never recognize HTTP/1.0 requests. The check
hm.proto.len > 8at line 2781 is always false becausemg_http_parse()enforcesproto.len == 8for valid requests. As a result,Transfer-Encoding: chunkedis accepted for HTTP/1.0 requests, violating RFC 7230 (chunked was introduced in HTTP/1.1). When Mongoose sits behind a proxy operating in HTTP/1.0 mode, this enables CL.TE request smuggling — the proxy usesContent-Lengthwhile Mongoose processes chunked encoding, allowing an attacker to inject arbitrary requests.Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:NAffected Version
mongoose.chttp_cb()Vulnerability Details
Root Cause
The HTTP version detection at line 2780–2781:
The condition
hm.proto.len > 8requires the protocol string to be longer than 8 characters. However,mg_http_parse()enforceshm->proto.len == 8for all valid HTTP requests (line 2032):Since
8 > 8is alwaysfalse,is_http_1_0is nevertrue. The guard at line 2783 that prevents chunked TE for HTTP/1.0 never activates:Smuggling Scenario
When Mongoose sits behind a reverse proxy operating in HTTP/1.0 mode:
Content-Length: 0andTransfer-Encoding: chunked. Since HTTP/1.0 does not define chunked TE, the proxy ignores theTransfer-Encodingheader and usesContent-Length: 0. It considers the request body empty and forwards everything after the headers as the start of the next request.Transfer-Encoding: chunked. It reads the chunked body, then treats any data after the0\r\n\r\nchunk terminator as a new pipelined request.Proof of Concept
Steps to Reproduce
Expected (correct) Output
Actual Output (vulnerable)
Output Analysis
> 8(buggy)>= 8(fixed)GET / HTTP/1.0\r\n...GET / HTTP/1.1\r\n...The bug is unconditional — every HTTP/1.0 request is misidentified.
Impact
option http-server-close, legacy load balancers, and embedded gateway appliances using HTTP/1.0 upstream. Mongoose is widely deployed in IoT/embedded devices, which are frequently accessed through reverse proxies, VPN appliances, or cloud gateways that may use HTTP/1.0 for backend connections.Suggested Fix
Change
>to>=at line 2781:Patched In
Fixed in the 2026-06-23 Mongoose release (SBOM
7.21_1ddf6c2cbd_5ab08203or later).References