In the previous post, we looked at an HTTP/2 downgrade attack where we injected CRLF characters into a header and that allowed us to smuggle the Transfer-Encoding header through the H2 frontend. If the Transfer-Encoding header was provided as a standard header as part of the client-side request, the frontend would strip the header off. By smuggling the header through utilizing CRLF characters, the backend would honor the TE header which allowed us cause a desync. For this lab, we are going to be looking at a different way to use the CRLF characters within the headers such that we’re able to smuggle an entire request to the backend.
In this next lab, we have to go a bit deeper into the differences between how HTTP/1.1 and HTTP/2 are transferred over the wire and then ultimately processed by a web application. Once again, we are going to be going after an HTTP/2 downgrade vulnerability, and there is quite a bit of nuance. In this case, we’re going to identify a way to reflect content back to us (the attacker) that contains sensitive information (cookies) from an unsuspecting victim user. Additionally, there’s some difficult to find Burp functionality that makes solving this lab without some help incredibly difficult.
With that being said, I really enjoyed this lab, this attack vector, and I learned a lot in the solving of the lab and construction of this post.
This is the 13th blog post in the series I am publishing dealing with Request Smuggling or Desync vulnerabilities and attacks. These posts align to the PortSwigger Web Security Academy labs (here).
Lab: HTTP/2 request splitting via CRLF injection
This is post #13 of the series. Previous posts here:
- CL.TE Vulnerability
- TE.CL Vulnerability
- TE Header Obfuscation
- CL.TE & TE.CL via Differential Responses
- CL.TE Bypassing Frontend Security Controls
- TE.CL Bypassing Frontend Security Controls
- CL.TE Exploiting Frontend Request Rewriting
- CL.TE for Stealing Session Cookies
- Reflect XSS via Headers
- H2.TE Downgrade Attack
- H2.CL Downgrade Attack
- H2 Header CRLF Injection
Key content/reference material for understanding and exploiting the vulnerability:
- HTTP Specification
- HTTP 2 Specification
- HPACK Header Compression Specification for HTTP/2
This specific lab has a vulnerability due to the protocol downgrade, and by the way that HTTP/2 handles CRLF characters in headers.
Section 8.2.1 Field Validity from the HTTP/2 specification for header values:
A field value MUST NOT contain the zero value (ASCII NUL, 0x00), line feed (ASCII LF, 0x0a), or carriage return (ASCII CR, 0x0d) at any position.
If the (CR) or (LF) characters are included within a value attached to one of the headers in a request, that request should be treated as malformed and handled or dropped. Keep this in mind as we move through the identification and exploit of the vulnerability.
Note: more specific protocol analysis is provided in the previous lab.
Host Header Note: the Host header may point to different domains throughout the various screenshots and captured content:
Host: <Lab ID>.web-security-academy.net
This is due to the labs expiring on me at times during the construction of the blog post and the harvesting of the material.
The Goal: Poison the request queue, capture an admin’s cookie, use the cookie and delete the user carlos from the /admin panel.
Step 1: Recon. What does it look like if we try to access /admin?
We need credentials. Let’s run the playbook.
Can we POST to to the path ‘/’?
Yes. Can we provide both the CL and TE headers?
No. Can we provide both headers if we use HTTP/2 instead? Remember, switch to HTTP/2 in the Inspector and check ‘Allow HTTP/2 ALPN Override’ under the top level Repeater menu within Burp.
Yes. Can we tamper with Content-Length and get any different kind of response other than a 200?
No. Can we tamper with Transfer-Encoding and the request payload (conforming to TE spec and not) to get anything other than a 200?
No. Can we add a custom header?
Yes! Can this header handle CRLF characters on multiple requests?
No! For the most part, every 2 requests return a 400 error. If other users hit the backend, the queued \r\n gets released (which is what we want) so make sure and test multiple times to ensure you are getting the 400 response. This is gold!
Step 2: In this previous lab, this is where we injected to the Transfer-Encoding header. With the ‘\r\n’ within the header, this broke the TE header off post HTTP/2 to HTTP/1.1 downgrade which then allowed us to control the length of the processed payload. From there, we were able to leave smuggled content in queue as a result of the TE header. Let’s try the same here. Does the TE header have any bearing on the processing of the payload regardless of whether the payload conforms to spec and if there is any trailing content (3 different tests combined into a single for brevity)?
Yes, unfortunately, regardless of what is provided in the payload the web app now always responds with a 200 response. In this case, we’re not going to be able to cause a desync in this manner. If we can’t tamper with CL or TE really, but we can break the handling with the CRLF characters, what is another possible approach?
Step 3: Let’s try and smuggle an entire request! We want the app to throw an easily identified red flag if we’re able to smuggle an entire request. We know the /admin path will throw a 401 Unauthorized if try to access it without the right credentials, so let’s go ahead and use that as our smuggled request.
Note the ‘\r\n\r\n’ (2 CRLF) after the header value. The goal is to terminate the previous request and start a new one. To terminate a request, it requires 2 CRLF. Send the request a few times in rapid succession. As long as the admin user did not visit the page between your first and second visit, you will get back a 401 Unauthorized.
If you are able to get the 401, you have successfully poisoned the queue! The smuggled request is sitting within the backend and is being released by the next request. This is awesome!
It might take a few tries as the timing has to be just right, but eventually you will get back a 302. Within the 302 is a different user’s session cookie!
Step 4: Let’s use the cookie. Easiest way to access the /admin control panel is to simply inject the cookie into the browsers and then visit the page.
Delete the user carlos and the lab is solved.
Key items in this lab:
- Test for the CRLF handing whenever playing with a downgrade attack
- If the standard playbook for tampering with Content-Length and Transfer-Encoding does not work, try an entire payload