Request Smuggling WAF Bypass

Request Smuggling WAF Bypass

Written by

in

A WAF, a web application firewall, usually sits at the edge of a site and inspects every incoming request, blocking the ones that match known bad shapes before they reach the application. A request smuggling WAF bypass defeats that inspection without defeating any of its rules. The attacker hides a second request inside the body of a first request that the WAF reads as plain data, and the back end later parses that body as a fresh request the filter never saw. This is http request smuggling used to walk a blocked request straight past the one component built to stop it.

The reason it works is not a weak rule. The WAF can have a perfect list of forbidden paths and payloads. The bypass is that the WAF and the back end disagree about how many requests are even present in the bytes, so the dangerous request does not exist as a request at the moment the WAF looks.

What the WAF actually inspects

A WAF reads the request the way the front end frames it. It takes the request line, the headers, and a body whose length it reads from a length header, and it checks that unit against its rules. If the request asks for /admin and the rules block /admin, it is refused. If a header carries an injection string the rules know, it is refused. Inside its own view of the traffic, the WAF is doing its job correctly.

The trouble is that the WAF treats the body as opaque data. A body is just bytes to forward. So when an attacker sends a request whose body happens to contain the full text of another HTTP request, the WAF sees a normal request with a normal body and waves it through. Nothing in the body is a request as far as the WAF is concerned. It is cargo.

POST /submit HTTP/1.1
Host: acme-notes.example
Content-Length: 4
Transfer-Encoding: chunked

0

GET /admin HTTP/1.1
Host: acme-notes.example
Foo: 

To the WAF reading Content-Length: 4, the body is four bytes, the chunk terminator, and the request is a harmless POST /submit. The forbidden GET /admin below it is, in the WAF view, just more body bytes. It passes.

Why the request smuggling WAF bypass works at the back end

The back end reads the same bytes differently. It honors Transfer-Encoding: chunked, sees the 0 chunk, ends the first request there, and finds the GET /admin lines still sitting in its buffer. It parses them as a brand new request. That request is the forbidden one, and it arrives at the application having never been shown to the WAF as a request. The filter inspected the outer POST /submit and approved it. The inner GET /admin only became a request after it was already past the gate.

A request smuggling WAF bypass does not break a single rule. It makes the forbidden request invisible at the edge by keeping it disguised as body data until it is safely behind the filter.

This is why the familiar advice to inspect at the edge fails against a parsing disagreement. Inspection at the edge assumes the edge and the application see the same requests. A WAF can only check what it can parse as a request, and the whole attack is arranging for the dangerous request to not be parseable as one until it has moved past the checkpoint. The control is real and correct. It is simply looking at a different decomposition of the bytes than the back end will use.

What a bypass gets you

The point of slipping past the WAF is to reach what the WAF was guarding.

  • Blocked routes and methods. Paths the edge refuses, like administrative endpoints, become reachable because the smuggled request addressing them never passed through the edge as a request.
  • Blocked payloads. An injection string the WAF would catch in a header or parameter rides inside the smuggled request, so the rule that would match it never runs on it.
  • Everything a desync already grants. Once the boundary is broken, the attacker also inherits the ability to poison the shared connection or the cache in front of the app, since the same split that bypassed the filter also mismatches requests and responses.

The last point matters because a WAF bypass is rarely the final goal. It is a door into the other smuggling outcomes, and the same desynced connection can be driven into response queue poisoning once the filter is behind the attacker.

Defending when the filter is not enough

The honest conclusion is that a WAF cannot be the only line here, because the bug is upstream of what the WAF can see. The fixes are the desync fixes, and this is a parsing and input problem of the kind we group under injection and input.

  • Make the front end and WAF own the framing. The edge should rewrite each request into one unambiguous form with a single length it computed, so the back end parses the exact same requests the WAF inspected. If the edge will not honor a Transfer-Encoding, it should strip it rather than forward it.
  • Reject ambiguity instead of guessing. A request carrying both a Content-Length and a Transfer-Encoding should be refused and the connection closed, so no smuggled body can survive as a later request.
  • Normalize before you inspect. A filter that inspects after the request has been reduced to one canonical form is checking the same bytes the back end will run. Inspecting a shape the back end will reparse is the gap the bypass lives in.
  • Prefer HTTP/2 to the back end. End to end framing removes the text length the WAF and back end can read two ways.

Finding a bypass like this means reasoning about where two components disagree, not matching a signature, which is exactly what a filter cannot do for itself. When UnboundCompute reports a smuggling path through a WAF, it confirms the smuggled request actually reaches an impactful sink before reporting, so a claimed bypass is only a finding once the inner request is shown to hit something the edge was meant to protect. You can read how that proof works on our about page. The WAF was never wrong about its rules. It was wrong about how many requests it was holding.

Frequently asked questions

What is a request smuggling WAF bypass?

It is a way to defeat an edge filter without breaking any of its rules. The attacker hides a forbidden request inside the body of a request the WAF reads as plain data, and the back end later parses that body as a fresh request the filter never inspected as a request.

Why does inspecting at the edge not stop it?

Edge inspection assumes the edge and the back end see the same requests. A WAF can only check what it can parse as a request, and the attack keeps the dangerous request disguised as body data until it is past the filter, so there is nothing for a rule to match at the moment of inspection.

What does a bypass let an attacker reach?

Blocked routes and methods like administrative endpoints, payloads the WAF would otherwise catch, and every other smuggling outcome. Because the same desync that hid the request also mismatches requests and responses, the attacker can go on to poison the shared connection or a cache in front of the app.

How do you defend against it?

Treat the WAF as one layer, not the only one, because the bug is upstream of what it can see. Make the front end own the framing and rewrite each request into one unambiguous form, reject requests carrying both Content-Length and Transfer-Encoding, normalize before inspecting, and prefer end to end HTTP/2.


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.