This is the 6th 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). The first four posts deal with theory and focus on causing errors with a smuggled request. The last post dealt with bypassing frontend security controls to execute administrative functionality on the site via exploiting a CL.TE vulnerability. This post focuses on doing the exact same except the vulnerability is flipped to a TE.CL.
This is post #5 of the series. Previous posts here:
Key content/reference material for understanding and exploiting the vulnerability:
For this post, I am going to be focusing on a single lab from the Academy:
The Goal: same as the previous post – delete a user via the admin control panel with a smuggled request. The admin control panel is located on the path ‘/admin’ and the username is carlos.
Note 1: at this point in the labs I have been avoiding using any Burp extensions. The HTTP Request Smuggler extension is amazing and can really help with these types of vulnerabilities, however, by approaching the labs manually and avoiding any additional tooling it allows me to drive to a deeper understanding of each exploit which I hope is coming through on each of the labs.
Note 2: the Host header will point to different domains:
throughout the various screenshots. This is due to the labs expiring on me at times during the construction of the blog post and the harvesting of the material.
Let’s get started!
Step 1: Open the lab within the Burp provided preconfigured browser and browse to ‘/admin’. You’ll receive a 403 Forbidden response with the message:
"Path /admin is blocked"
Just like in the previous lab, we are not an admin, so this makes sense. There’s a defense in place that ensures the admin interface can only be accessed either with appropriate credentials or from an appropriate location.
Step 2: Now to find a path that is susceptible to smuggling. With the browser, send a request to ‘/’ and then grab the request from the HTTP history log within Burp and send to Repeater. Flip the GET to a POST. Include a body within the request and then send.
This works, so, we have potentially identified a soft spot to attack.
Step 3: Add in the Transfer-Encoding header. If we leave the payload within the body as is, it does not conform to expectations if Transfer-Encoding is honored by the web application (either frontend or backend).
This hangs for 15 seconds and then returns with a 500 error, so, either the frontend or the backend is probably honoring Transfer-Encoding.
Step 4: Update the payload to conform to Transfer-Encoding spec. At this point, just let Burp update the Content-Length (top menu Repeater –> Update Content-Length) header for ease. This will prove that the web application is handling the Transfer-Encoding header correctly.
This works. Now, we need to see if we can get the application to break and/or respond in a way that indicates that we do in fact have a request smuggling vulnerability on this route within the web application.
Step 5: We need to determine if a mismatch between Content-Length and Transfer-Encoding causes the web application to respond with an error or hang. If the web application (all components that handle the request) respond per HTTP specification, Content-Length should get ignored and the request should always be handled per the Transfer-Encoding header. In order to test, set the Content-Length to 1 shorter than the actual payload length and send the request. Make sure and turn off Repeater –> Update Content-Length from the top level menu.
This returns with a status code of 200 and the correct content. So far we know that a malformed TE payload with the TE header specified causes a 15 second hang, and that a CL of too short is handled just fine. Let’s try with 1 character longer.
This hangs for 10 seconds (consistently) and returns a 500 error with an XML body. The error we received in step 3 was a 500 error after 15 seconds and a text payload. These errors are being handled differently by the web application. What does this tell us?
- Since the payload has to conform to Transfer-Encoding spec or we always get a 500 after ~15 seconds, this means that the frontend is most likely honoring Transfer-Encoding.
- Since the timing of the error in the above screenshot is ~10 seconds, the payload conforms to Transfer-Encoding spec, and it only hangs when an insufficient amount of content is provided (payload is shorter than 25 specified in the Content-Length header), this means the backend is most likely honoring Content-Length over Transfer-Encoding. When we specify a payload longer than the Content-Length header, the request gets processed just fine which makes sense since Content-Length payloads do not have to end with a special set of characters to terminate – it will simply truncate.
We have our soft spot! And, as expected per the name of the lab, we’re looking at a TE.CL vulnerability.
Step 6: Since the frontend is honoring Transfer-Encoding and the backend is honoring Content-Length, we’re going to need to embed our smuggled payload within the TE payload and then use the CL header to cut the payload in the appropriate spot.
Here’s the payload we want to use. Navigate the interactive controls to see a breakdown of the payload and the logic behind construction:
When we submit this payload twice (second time to release the request on the backend), we get a 401 Unauthorized. This is great!
Step 7: When an interface is only available to local users, that often means that it has to be accessed from http(s)://localhost, http(s)://127.0.0.1 or some other method of specifying local access. If we specify the host within our smuggled payload, it is possible the backend will honor our host header and treat the request as if it is originating from the local backend server.
On the second request, the admin control panel is returned with the option to delete carlos. All we had to do was include the Host header specifying ‘localhost’ and update the hexadecimal value for the TE payload. If we miss updating the value in the payload, the frontend (which honors TE) will throw a 500 error as in step 3.
Quick note: now that we know a Host header set to localhost will be required to access the admin control panel, the question becomes whether or not we can simply set the Host header to localhost and forgo the effort of smuggling for the deletion step. The answer is no – the frontend will not allow a request to /admin regardless of Host header specified in the main request.
Step 8: To solve, the last step is to update the path specified in the payload, once again update the hexadecimal value for the length of the TE payload, and submit.
Key items in this lab:
- Since the frontend honors Transfer-Encoding, pay extra special attention to the hexadecimal value in the payload
- With an entire request being queued within the payload, watch the Content-Length within the second request (payload) to ensure you cause queuing
- Really read the errors that are returned in the responses. Key indicators such as needing to be local are often revealed