HTTP request smuggling: how it works, how testers prove it, how to fix it

HTTP request smuggling happens when a front-end proxy and a back-end server disagree about where one request ends and the next begins. An attacker hides a partial request that the back-end attaches to another user's request. Testers use timing and differential probes on test connections. Fixes are HTTP/2 end to end, strict parsing, no ambiguous requests and patched servers.

What is HTTP request smuggling?#

HTTP request smuggling is a flaw where two servers in the same request path disagree about where one HTTP request ends and the next begins. A front-end (a CDN, reverse proxy or load balancer) and a back-end (the application server) share one connection, and the attacker sends bytes that the front-end counts as one request and the back-end counts as a request plus the start of another. The back-end then glues that leftover onto the next request on the connection, which can belong to another user. CWE-444 calls this inconsistent interpretation of HTTP requests.

The rule in RFC 9112 shows where the disagreement starts. A message with both Transfer-Encoding and Content-Length should be handled as an error, because Transfer-Encoding overrides Content-Length and the pair "might indicate an attempt to perform request smuggling". Servers that apply the rule differently, or that parse a malformed header differently, produce a desync.

Testing for smuggling needs written authorization from the system owner. A bad probe can disturb real users.

How does HTTP request smuggling work?#

Request smuggling works because HTTP/1.1 has two ways to give a body length, Content-Length and Transfer-Encoding: chunked, and a chain of servers may pick different ones. If the front-end uses one and the back-end uses the other, the byte stream splits at different points.

Attacker sends one request on a shared connection:

  [ POST /search ... body ... | leftover bytes "GET /404 ..." ]
   ^---- front-end sees ONE request (uses Content-Length)
   ^---- back-end sees TWO: the POST (uses chunked, ends at "0"), then the start of a request

Next user's request arrives on the same back-end connection:

  [ leftover "GET /404 HTTP/1.1 Foo: x" + "GET /account HTTP/1.1 ..." ]
   -> the back-end reads one merged request

CL.TE desync on a fictional shop

This is a demonstration. shop.example.com is a fictional host behind a fictional proxy, and no real customer system was tested. The front-end uses Content-Length and the back-end uses Transfer-Encoding (a CL.TE pair). The test runs in a staging copy with only the tester's own requests.

The tester sends this "attack" request (the Content-Length of 49 counts the bytes through Foo: x):

POST /search HTTP/1.1
Host: shop.example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 49
Transfer-Encoding: chunked

e q=smuggling&x= 0

GET /404 HTTP/1.1 Foo: x

Then the tester sends a normal request on the same connection:

POST /search HTTP/1.1
Host: shop.example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 11

q=widgets

Safe result: the second response is the normal search page. Vulnerable result: the second response is a 404, because the back-end read GET /404 HTTP/1.1 Foo: xPOST /search ... as one request. The pattern is the one PortSwigger documents for differential confirmation.

Browsers and HTTP/2 changed the picture. PortSwigger's research HTTP/2: The Sequel is Always Worse (5 August 2021) showed that front-ends which accept HTTP/2 and rewrite it to HTTP/1.1 for the back-end create new desync types. Browser-Powered Desync Attacks (10 August 2022) showed desyncs that a victim's own browser can trigger, without a special proxy in the path.

Which variants of request smuggling exist?#

The main variants are named after the front-end and back-end behavior: CL.TE, TE.CL, TE.TE, CL.0, H2.CL and H2.TE. In each name, the first part is the header the front-end uses and the second is the header the back-end uses.

Variant Front-end uses Back-end uses Detection idea
CL.TE Content-Length Transfer-Encoding Timing probe: back-end waits for the next chunk
TE.CL Transfer-Encoding Content-Length Timing probe: back-end waits for more body bytes
TE.TE Transfer-Encoding Transfer-Encoding One server ignores a malformed header; it then behaves as CL.TE or TE.CL
CL.0 Content-Length Nothing (ignores the body) Follow-up request on the same connection returns an unexpected response
H2.CL HTTP/2 length Content-Length after downgrade Injected content-length disagrees with the real HTTP/2 body
H2.TE HTTP/2 length Transfer-Encoding after downgrade Injected transfer-encoding header survives the downgrade

PortSwigger's 2022 research calls CL.0 a known but lesser-explored attack class, and its Academy page notes that sites using HTTP/2 end to end are immune to the classic attacks because HTTP/2 has one mechanism for message length. TE.TE depends on a header that one server rejects and another accepts, and it is a parser quirk of the specific products in the chain, so we do not list obfuscated header variants here.

Which real cases and CVEs involve request smuggling?#

Both CVEs below are in widely deployed server software, and the research dates show when the classes became known. Each entry links to NVD or the original post.

Date Case What the source says
14 July 2022 CVE-2022-32213, Node.js llhttp NVD: the llhttp parser before v14.20.1, v16.17.1 and v18.9.1 did not correctly parse and validate Transfer-Encoding headers and could lead to HTTP request smuggling. CWE-444. NVD base score 6.5 (Medium).
7 March 2023 CVE-2023-25690, Apache HTTP Server mod_proxy NVD: Apache HTTP Server 2.4.0 through 2.4.55 with mod_proxy plus some RewriteRule or ProxyPassMatch configurations is affected when a non-specific pattern matches part of the user-supplied request-target and re-inserts it into the proxied request. Impact includes bypass of proxy access controls and cache poisoning. Fixed in 2.4.56. NVD base score 9.8 (Critical).
5 August 2021 HTTP/2: The Sequel is Always Worse, PortSwigger Research New classes of HTTP/2-exclusive threats, including downgrade smuggling.
10 August 2022 Browser-Powered Desync Attacks, PortSwigger Research Desync attacks that do not need a proxy chain controlled by the attacker, including CL.0.

The Apache case shows that the vulnerable component can be a rewrite rule and not a bug in a parser: a configuration pattern put user input into the proxied request line. Check your own rewrite and proxy rules for user input in the request target.

Which parts of your stack can cause a desync?#

A desync needs two HTTP/1.1 hops, so list every hop between the client and the application code, and check each one. The risk sits at the boundaries, so each row is a boundary.

Boundary What to check Fix
CDN or WAF to your load balancer Does it accept HTTP/2 and downgrade? Does it normalize Transfer-Encoding? Prefer HTTP/2 to origin; reject requests with both Content-Length and Transfer-Encoding
Load balancer or reverse proxy to app server Does it reuse back-end connections across users? Which HTTP version does it speak? Use HTTP/2 to the back-end where supported; disable back-end connection reuse if you cannot
Proxy with rewrite rules (Apache, nginx) Does user input reach the proxied request line? Patch to a fixed version; avoid non-specific patterns in RewriteRule and ProxyPassMatch
App server HTTP parser (Node.js, Gunicorn, Tomcat, and others) Is the parser version patched? Upgrade to fixed versions, as with llhttp
Internal service-to-service proxies Do sidecars or gateways downgrade HTTP/2? Same rules as the edge

Configuration lines that help:

# nginx: speak HTTP/1.1 to the upstream so keepalive works predictably.
# The nginx docs say the default changed to 1.1 in 1.29.7; earlier versions defaulted to 1.0.
location / {
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_pass http://app_backend;
}
# HAProxy: speak HTTP/2 to the back-end so message length has one definition.
backend app
    server app1 10.0.0.10:8443 ssl verify required ca-file /etc/ssl/ca.pem alpn h2 proto h2

Use these only after you confirm that your back-end supports HTTP/2. The HAProxy manual documents the proto keyword for forcing the protocol on a server connection. The alpn setting must match what the back-end offers.

PortSwigger's prevention advice adds four points. Use HTTP/2 end to end and disable downgrading if possible. Make the front-end normalize ambiguous requests, and make the back-end reject any that remain. Never assume requests have no body, which is the root of CL.0 and client-side desync. Close the connection if the server hits an exception while handling a request. Disabling back-end connection reuse helps against some attacks, but PortSwigger notes it does not stop request tunnelling.

How do penetration testers detect and prove request smuggling?#

Testers detect smuggling with a timing probe, confirm it with a differential probe on their own connection, and record the evidence. PortSwigger calls timing the most generally effective detection method, and Burp Scanner automates it. The OWASP Web Security Testing Guide covers the topic in WSTG-INPV-15.

The CL.TE timing probe from PortSwigger's guide:

POST / HTTP/1.1
Host: vulnerable-website.com
Transfer-Encoding: chunked
Content-Length: 4

1
A
X

The front-end forwards only part of the body and omits the X. A CL.TE back-end processes the first chunk and waits for the next, so the response is delayed. The TE.CL probe reverses the idea: the front-end uses the chunked body, and a TE.CL back-end waits for more bytes than it received.

POST / HTTP/1.1
Host: vulnerable-website.com
Transfer-Encoding: chunked
Content-Length: 6

0

X

PortSwigger advises running the CL.TE test first and the TE.CL test only if the first one fails, to be stealthy and minimize disruption.

The steps:

  1. Get written authorization, a staging environment that mirrors production, and a test window.
  2. Map the hops: CDN, load balancer, proxy, app server, and the HTTP version at each.
  3. Send the timing probes and record the delay against a baseline measured several times.
  4. Confirm with a differential probe: an attack request, then a normal request on the same connection, as in the demo above. Use your own session and a path that you created for the test.
  5. Run controls: the same requests without the smuggled part, and a request to a nonexistent path, to show that the odd response comes from the desync.
  6. Save the raw requests and responses with timestamps.

Burp Scanner automates the timing probes. Our method also applies here: a finding needs a real, reproduced effect, and we do not report a delay alone as a confirmed vulnerability. Poisoning a shared connection on a production system is out of bounds unless the owner approves the risk in writing.

Sample evidence record (demonstration)

This is a demonstration with invented numbers. No real system was tested. Timing baseline (normal request, 10 runs): 90 to 140 ms. CL.TE probe, 5 runs: 10.0 s each, matching the back-end read timeout. Differential probe: normal request after the attack request returned 404; the same normal request without the attack request returned 200. Test path and connection were created and owned by the tester. Timestamps and request files are saved with a SHA-256 manifest.

How do you prevent HTTP request smuggling?#

Use HTTP/2 end to end, reject ambiguous HTTP/1.1 requests, and patch every proxy and server in the path. These steps are the same on any stack, so apply them at the edge first.

  1. Run HTTP/2 between the front-end and the back-end. HTTP/2 gives each message one length mechanism.
  2. Configure the front-end to reject requests that carry both Content-Length and Transfer-Encoding, as RFC 9112 advises treating them as an error, and to reject malformed header lines.
  3. Keep proxies, load balancers and language runtimes on fixed versions. CVE-2022-32213 was fixed in new releases of Node.js's parser, and CVE-2023-25690 in Apache 2.4.56.
  4. Review rewrite and proxy rules so user input never fills part of the proxied request line.
  5. Turn off back-end connection reuse across users if you cannot move to HTTP/2.
  6. Close the connection after a server-level error.

A WAF rule that blocks one probe does not remove the parsing disagreement. Patch the parsers.

Request smuggling testing checklist#

  1. Draw the request path and the HTTP version of each hop.
  2. Confirm authorization, a test window and a staging copy.
  3. Check which hops downgrade HTTP/2 to HTTP/1.1.
  4. Send the CL.TE timing probe, then the TE.CL probe only if needed.
  5. Test H2.CL and H2.TE where the front-end accepts HTTP/2.
  6. Test CL.0 on endpoints that normally take no body, such as static files and redirects.
  7. Confirm with a differential probe on your own connection.
  8. Run controls and record the baseline.
  9. Check cache behavior and response queues for effects on other requests, in staging only.
  10. Verify versions of proxies and servers against NVD.

When should you get a professional test?#

Get a professional test when you run more than one HTTP layer in front of the application, such as a CDN, a load balancer and a reverse proxy, or when you recently changed proxies or enabled HTTP/2. Smuggling needs careful scoping, because probes can touch shared connections. A web application penetration test covers the whole path, including how the layers handle requests, and an API penetration test covers API gateways. Related reading: web cache poisoning and host header injection.

Questions

Does a WAF stop HTTP request smuggling?

Not reliably. The WAF is often the front-end that parses the request differently from the back-end, so it can be part of the problem. The root cause is two parsers disagreeing about message length. Fix the parsing: use HTTP/2 to the back-end, reject ambiguous requests, and patch the proxy and the server.

Is HTTP/2 enough to prevent request smuggling?

HTTP/2 end to end is enough for the classic attacks, because it has a single way to set message length. PortSwigger says sites that use HTTP/2 end to end are inherently immune to them. Sites that downgrade HTTP/2 to HTTP/1.1 at the back-end can still be vulnerable (H2.CL and H2.TE).

What is the difference between request smuggling and cache poisoning?

Request smuggling is a parsing disagreement that lets an attacker affect the request stream. Web cache poisoning is making a cache store a harmful response. They combine: PortSwigger lists web cache poisoning as one way to exploit a smuggling flaw. Cache poisoning can also happen without any smuggling.

What is CL.0 request smuggling?

CL.0 is a desync where the back-end ignores the Content-Length header and treats the request as having no body, so the body becomes the start of the next request. PortSwigger describes it as equivalent to a Content-Length of 0. It needs no Transfer-Encoding header, and it was shown with browser-powered attacks in 2022.

Is it safe to test for request smuggling on a live site?

Only with care and written permission. A probe can desynchronize a connection that other users share. Test in a staging environment that mirrors production, use a timing probe first, poison only a test path with your own requests, and agree a time window with the owner.

Sources

  1. PortSwigger Web Security Academy: HTTP request smuggling (checked 9 November 2026)
  2. PortSwigger Web Security Academy: Finding HTTP request smuggling vulnerabilities (checked 9 November 2026)
  3. PortSwigger Research: HTTP/2: The Sequel is Always Worse (James Kettle, 5 August 2021) (checked 9 November 2026)
  4. PortSwigger Research: Browser-Powered Desync Attacks (James Kettle, 10 August 2022) (checked 9 November 2026)
  5. RFC 9112: HTTP/1.1, section 6.3 Message body length and section 11.2 Request smuggling (checked 9 November 2026)
  6. CWE-444: Inconsistent Interpretation of HTTP Requests (HTTP Request/Response Smuggling) (checked 9 November 2026)
  7. OWASP WSTG-INPV-15: Testing for HTTP Splitting/Smuggling (checked 9 November 2026)
  8. NVD: CVE-2023-25690 (Apache HTTP Server mod_proxy) (checked 9 November 2026)
  9. NVD: CVE-2022-32213 (Node.js llhttp) (checked 9 November 2026)
  10. nginx documentation: ngx_http_proxy_module, proxy_http_version (checked 9 November 2026)
  11. HAProxy 3.0 configuration manual: server keyword proto (checked 9 November 2026)