Skip to content

Back to the catalog

portswigger.netarticle

CRLF-Powered Desync Attacks (PortSwigger Research)

PortSwigger Research piece on using CRLF injection to trigger HTTP desync/request-smuggling conditions, extending prior request-smuggling research into new attack variants. Full body text was not available for this classification pass, so specifics (exact techniques, affected vendors, PoCs) could not be verified here.

TL;DR PortSwigger research on CRLF-injection-driven HTTP desync attacks; content unverified this pass, classified from title/URL only.
Document

What it is

"CRLF-Powered Desync Attacks," a PortSwigger Research paper (published 5 August 2026) co-authored by James Kettle's team and Tobia Righi (TurtleSec), presented at Black Hat USA and DEFCON. It escalates HTTP header injection into full HTTP request-smuggling/desync — up to a self-replicating "desync worm" — and shows detection and exploitation methods that work even when connections aren't shared cross-user. An open-source Burp extension, nuclei templates, and labs are released on the authors' GitHubs.

How it works

The root cause is an Nginx misconfiguration: when $uri (or $document_uri) is used in proxy_pass (e.g. proxy_pass http://backend$uri;), Nginx normalises and URL-decodes the path before forwarding, decoding %0d%0a into real CRLFs. This lets an attacker inject arbitrary lines into the upstream request. The same flaw in a return 302 ...$uri directive yields response-header injection (response splitting). OpenResty and Tengine inherit the issue.

From an injectable CRLF the paper builds several escalations:

  • Response Queue Poisoning (RQP): split into exactly two complete requests (double CRLF), desyncing the response queue so victims receive each other's responses — harvesting session cookies/auth tokens. Demonstrated inside a CDN's infrastructure and a payment provider's Kubernetes cluster (leaking credit-card/PII across tenants).
  • Single-header desyncs: where double-CRLF is blocked, injecting one Transfer-Encoding header alongside Content-Length gives a CL.TE desync (recommended over the trickier 0.CL).
  • Tunnelling / connection-locked / IP-locked desyncs, normally not cross-user, are exploited by moving the attack into the victim's browser via fetch() or navigation (most CRLF desyncs are fetch-spec compatible), enabling the worm. The HEAD technique plus the Range header lets you over-read a back-end response to arbitrary length to plant reflected XSS.
  • Injection point need not be the path — cases landed in a custom header, a session cookie, and the request path via cookie reflection.

Detection primitives that produce predictable status codes: invalid HTTP version → 505; injected Transfer-Encoding → 501; and the Expect: asdf header → 417 Expectation Failed. The Expect: 100-continue header also makes Nginx over-read tunnelled responses and can make front-ends forget to strip internal headers (e.g. x-fd-int-roxy-origin-ip).

Preconditions / Affected

Nginx (or OpenResty/Tengine) front ends using $uri/$document_uri in proxy_pass/return, or a variable whose regex match fails to exclude whitespace. Confirmed live against a CDN, a telecoms provider, a payment provider, a clothing store, a phone manufacturer, a streaming service, a social-media CDN, a car manufacturer, and a TikTok domain (Set-Cookie injection). A Discord desync is also cited.

What to do

Per the paper's remediation:

  • Remove $uri and $document_uri from proxy_pass and return directives entirely.
  • Do not create a variable with a regex match that fails to exclude whitespace.
  • Check OpenResty and Tengine configs for the same patterns.
  • For a permanent fix, enable HTTP/2 upstream (per "HTTP/1.1 Must Die").

Caveats

The original 2004 response-splitting desync is now largely killed by the stacked-response/over-read problem, which the authors could not bypass. WAFs often block %0d%0a in insertion points (they point to Unicode-normalization bypasses). Reflected XSS via response splitting normally fails because injection occurs after the Location header on a redirect — bypassed here only via Cloudflare's CDN-Cache-Control: private="Location" to strip that header. Several case-study targets are described but not named.