When an attacker desyncs a connection between a front end and a back end, the damage does not stop at one smuggled request. response queue poisoning is the lasting state that follows: once the two servers disagree about where one request ended, the responses coming back fall out of step with the people who asked for them, and every later user on that connection receives an answer meant for someone else. It is one of the harshest outcomes of http request smuggling, because a single well timed request can keep handing strangers each other’s pages until the connection is torn down.
The reason it lasts is that HTTP on a reused connection is a strict queue. The back end reads requests in order and writes responses in the same order, trusting that the first response belongs to the first request, the second to the second, and so on. Nobody labels a response with whose request it answered. The ordering is the only link. Break the ordering once and nothing on the connection puts it back.
How the queue shifts by one
Start from a desynced connection where the attacker has left a smuggled prefix in the back end buffer. That prefix is the beginning of a request that the back end will finish by gluing on whatever bytes arrive next. Now walk the queue one step at a time.
The attacker sends their smuggled request, which the back end reads as request one and answers with response one. But because of the leftover prefix, the boundaries are now off by one. The back end has produced one more response than the front end expected, or it has merged two requests into one, so the count of responses no longer matches the count of real requests. From this point the mapping is shifted. When the next real user sends their request, the back end hands back a response that was actually generated for the request before theirs.
victim sends: GET /account HTTP/1.1 victim receives: 200 OK (body: a different user's /account page)
That is the whole trick in one line. The victim asked for their own account page and received a response the back end built for the request that sat ahead of theirs in the shifted queue. The queue shifting does not undo itself. Each user pulls the response meant for the previous request, so the poisoning rides the connection forward, victim after victim, for as long as the connection stays open and pooled.
Response queue poisoning is an off by one error in who gets which answer. The requests are fine and the responses are fine. Only the pairing between them is wrong, and nothing on the connection ever repairs it.
Why response queue poisoning is so damaging
Consider what a response can contain. On a logged in connection the back end returns pages built for an authenticated session: an account dashboard, an order history, a password reset form with a token already filled in, an API reply carrying a bearer value. When the queue is shifted, those pages land on the wrong browser. The attacker does not need to read a cookie or crack a session. They receive a fully rendered authenticated page that the server addressed to the previous request in line.
Three properties make this worse than a one shot leak.
- It is persistent on the connection. One smuggled request poisons the ordering, and every subsequent response on that connection stays shifted until the connection closes. The attacker can keep collecting answers meant for others without sending another attack.
- It crosses users. The pooled connection carries traffic from many people. The attacker’s request and the victim’s request share the same pipe, so the attacker harvests responses built for strangers, not for themselves.
- It needs no secret. There is no credential to steal. The server volunteers the private page, correctly rendered, to whoever happens to hold the shifted slot in the queue.
The same shift can be driven the other direction as well. Instead of waiting to receive a victim’s response, an attacker can store a prefix that captures the victim’s incoming request, a technique we cover in capturing other users requests. Response queue poisoning is the receiving side of that coin: you are not reading what the victim sent, you are reading what the server sent back to them.
Where the desync usually comes from
Any desync can start the shift. A length header disagreement in a CL.TE or TE.CL chain is the common source, but a timing split works just as well, which is why a pause based desync attack ends in the same poisoned queue. The queue does not care how the boundary moved. It only cares that the count of responses stopped matching the count of real requests, and once that happens the pairing slides.
Breaking the queue shift
The defenses aim at two things: never let a boundary move, and limit the blast radius if one does. This class of flaw sits in how a server parses request boundaries, which is why we file it under injection and input rather than treating it as a content problem.
- Close the connection on anything ambiguous. A server that sees a request it cannot parse one clear way should respond and then close, so no shifted state survives to the next user. Closing is the step that drains the poison.
- Normalize framing at the front end. The front end should rewrite every forwarded request into one unambiguous form with a single length it computed, so the back end never counts bytes differently and the queue never shifts in the first place.
- Limit connection reuse across users. Some deployments stop pooling one back end connection across different users, so even a successful shift cannot deliver one person’s response to another.
- Prefer HTTP/2 to the back end. End to end HTTP/2 framing marks message boundaries explicitly, so there is no text length for two servers to read two ways.
Finding this class means reasoning about an ordering that no single request reveals, so a scanner that checks one request at a time tends to miss it. When UnboundCompute investigates a desync, it confirms the smuggled request actually reaches an impactful sink before reporting, which here means proving that a real victim response actually lands on the wrong connection rather than guessing from a timing hint. You can read how that works on our about page. The queue looks orderly from the outside. Response queue poisoning is the proof that order was only ever a convention, held together by a count that an attacker can move by one.
Frequently asked questions
What is response queue poisoning?
It is the lasting state after a connection is desynced, where the back end responses stop matching the requests that asked for them. The pairing shifts by one, so every later user on that pooled connection receives a response that was built for the request ahead of theirs.
Why does the poisoning persist?
HTTP on a reused connection is a strict queue with no label tying a response to its request. The order is the only link. Once a smuggled request makes the response count stop matching the request count, nothing on the connection restores the pairing, so it stays shifted until the connection closes.
Why is it so damaging?
On a logged in connection the shifted response can be a fully rendered authenticated page, such as an account dashboard or a reply carrying a token. The attacker receives a private page the server addressed to another user, with no cookie to steal and no credential to crack.
How do you stop it?
Close the connection after any request a server cannot parse one clear way, normalize framing at the front end so boundaries never move, limit back end connection reuse across users, and prefer end to end HTTP/2 whose framing marks message boundaries explicitly.
Put an autonomous researcher on your own systems
UnboundCompute is an autonomous security researcher that reasons about how an application fits together and proves the access control and injection bugs it finds. We are opening a small number of founding design partner seats: private early access pointed at a staging target you choose, and a say in what it looks for. If your team ships software worth pressure testing, apply to the design partner program.
