Web cache poisoning via HTTP/2 request tunnelling
This lab is vulnerable to request smuggling because the front-end server downgrades HTTP/2 requests and doesn’t consistently sanitize incoming headers.
The front-end server doesn’t reuse the connection to the back-end, so isn’t vulnerable to classic request smuggling attacks. However, it is still vulnerable to request tunnelling.
Reproduction and proof of concept
Send a request for
GET /to Burp Repeater. Expand the Inspector’s Request Attributes section and change the protocol to HTTP/2.
Using the Inspector, try smuggling an arbitrary header in the
:pathpseudo-header, making sure to preserve a valid request line for the downgraded request as follows:
/?cachebuster=1 HTTP/1.1\r\n Foo: bar
Observe that you still receive a normal response, confirming that you’re able to inject via the
Change the request method to
HEADand use the
:pathpseudo-header to tunnel a request for another arbitrary endpoint as follows:
/?cachebuster=2 HTTP/1.1\r\n \r\n Host: 0af7002003f835c3c4e2c6d8005000ac.web-security-academy.net\r\n \r\n GET /post?postId=1 HTTP/1.1\r\n Foo: bar
Note that we’ve ensured that the main request is valid by including a Host header before the split. We’ve also left an arbitrary trailing header to capture the
HTTP/1.1 suffix that will be appended to the request line by the front-end during rewriting.
Send the request and observe that you are able to view the tunnelled response. If you can’t, try using a different
Remove everything except the path and cachebuster query parameter from the
:pathpseudo-header and resend the request. Observe that you have successfully poisoned the cache with the tunnelled response.
Now you need to find a gadget that reflects an HTML-based XSS payload without encoding or escaping it. Send a response for
GET /resourcesand observe that this triggers a redirect to
Try tunnelling this request via the
:pathpseudo-header, including an XSS payload in the query string as follows.
/?cachebuster=3 HTTP/1.1\r\n Host: 0af7002003f835c3c4e2c6d8005000ac.web-security-academy.net\r\n \r\n GET /resources?<script>alert(1)</script> HTTP/1.1\r\n Foo: bar
Observe that the request times out. This is because the
Content-Length header in the main response is longer than the nested response to your tunnelled request.
From the proxy history, check the
Content-Lengthin the response to a normal
GET /request and make a note of its value.
Go back to your malicious request in Burp Repeater and add enough arbitrary characters after the closing
\scripttag to pad the reflected payload so that the length of the tunnelled response will exceed the
Content-Lengthyou just noted.
Send the request and confirm that the payload is successfully reflected in the tunnelled response. If it still responds with a timeout, there is not enough padding yet.
While the cache is still poisoned, visit the home page using the same cachebuster query parameter and confirm that the
In the Inspector, remove the cachebuster from the request and resend it until you have poisoned the cache. Keep resending the request every 5 seconds or so to keep the cache poisoned until the victim visits the home page and the lab is solved.
An attacker will need to poison the cache in such a way that when the victim visits the home page, their browser executes
alert(1). A victim user will visit the home page every 15 seconds.
Note: This lab supports HTTP/2 but doesn’t advertise this via ALPN. To send HTTP/2 requests using Burp Repeater, you need to enable the Allow HTTP/2 ALPN override option and manually change the protocol to HTTP/2 using the Inspector. Please note that this feature is only available from Burp Suite Professional / Community 2021.9.1.