User
EarlyAccess is a hard rated machine on HackTheBox created by Chr0x6eOs. For the user part we will first abuse a XSS vulnerability in a contact form to obtain the admin’s cookie. From there we can download a backup of a key verification script. This allows us to generate valid keys and register a game to access another vhost. On this vhost we discover an SQLI which results in hashes from the database, giving us access to yet another vhost after cracking. Fuzzing a parameter in a not yet fully implemented app we are able to include local files and get the source code of another functionality. Analyzing it we discover a possible RCE. Getting a shell the credentials from the database are reused and we are able to move laterally to another user. This user has api credentials stored in his .wgetrc
which we can use to retrieve another set of credentials. This enables us to ssh into the machine and grab the user flag. For the root part we will abuse the restart of a docker container in which we can control the entrypoint script. The docker has a shared directory with the host which let’s us copy a suid sh
and escalate to root on the host system.
Nmap
As usual we start our enumeration off with a nmap scan against all ports, followed by a script and version detection scan against the open ones to get an initial overview of the attack surface.
All ports
1 |
|
Script and version
1 |
|
XSS
The nmap scan show there are 3 ports open on the machine and it also leaks a hostname which we add to our /etc/hosts
. Browsing to the page we see the homepage of a game which also lets us register an new user.
Being logged in after registering the web app has several functionalities. We can register a game key but we don’t have a valid one yet. What looks interesting is that we can write a message to the administrator user.
The subject and the body seem to get properly url encoded, the username paramter seems to be vulnerable to XSS though. To abuse this we first change our username to a XSS payload that grabs the admin’s cookie.
1 |
|
We set up our python webserver to retrieve the request and send a message.
1 |
|
After about a minute we get a reply containing the admin’s cookie.
1 |
|
Exchanging our cookies we are now logged in as admin and have access to an admin panel.
There are also additional Game
and Dev
blades which lead to different vhosts. Going over to game it seems we first need to register a valid game key to our account to access it.
On dev it seems we can only log in with the admin’s email address and password.
What looks interesting though is that we have access to a backup validator app, which checks if the game key sent is valid. The page also mentions that the magic_num
parameter is dynamically changed and needs to be synced with the api.
Generate Key
We download the backup and open the zip which only contains one python script.
1 |
|
There are five blocks in the game key and the script checks everyone of them after another. It also checks if the key passes a general regex check.
validate.py
1 |
|
We can use this script to build our own key generator. For the first piece we can just check all possible combinations to pass the validator for the first 3 letters. The next 2 characters need to be numbers and have to be distinct ones. This results in something like KEY01
. The second part checks if the byte value of the characters at even index is the same as the byte value of characters at odd index. A possible solution here is 0H0H0
. The next part has to start with XP
, also the combined sum has to be the same as the magic_num
parameter. Since we don’t know this paramter we can generate all possible combinations for it. The fourth piece just has to result in a fixed value when xored with the first piece, which we can easily reverse. This means the fourth piece is GAME1
in this case. The fifth piece is a checksum of the byte value of all other characters, so it has to be generated for all possible pieces in third place.
gencode.py
1 |
|
Running this script generates 60 valid keys for all possible values of magic_num
.
1 |
|
To brute it we will use burp intruder since it deals with the session handling easily. We take a key in valid format and intercept the request.
Then we select the position we want to brute force and select our earlier generated list as sniper payload.
The free version of intruder is quite slow but is enough in this case since it are only 60 requests. If it fails the magic_num
might have been exchanged during the attack and we just have to run it again.
SQLI
After the attack finished we have now access to the game vhost where we can play a round of snake.
Failing gracefully at it and going to the scoreboard we see a quite descriptive sql error message. This is interesting since we still have our XSS payload with quotes as username indicating SQLI in the username parameter.
Changing our username we can fix the query proving control over the injection.
First we identify the column number to be three using union injection.
Next we need to find a coulmn where we can display text in to exfiltrate data. This works for the second column.
Now we can simply first extract the database scheme which only contains the db
database next to the default.
After this we can extract the tablenames inside the database. Here the users
table looks like it could contain valuable information.
Checking the column names the suspicion seems to be right and in the next query we are able to retrieve the database hashes with emails and usernames.
If we are able to crack the admin hash we might be able to log into the dev vhost. Luckily for us it is a pretty weak password and cracks almost instantly.
1 |
|
1 |
|
LFI and RCE
Now we are able to log into the dev vhost. The page has two functionalities a file tool and a hashing tool.
Checking out the source of the hash tool we see that the php scripts seem to be stored in the /actions
folder.
Testing for the the file tool it is also present in the folder under the name file.php
, we need a paramter to use it first though.
Fuzzing for the parameter with ffuf we identify it to be filepath
.
1 |
|
Using this parameter we are now able to retrieve the source code of hash.php
with the php://filter
wrapper to convert it to base64.
The script takes multiple parameters. What is interesting is that it takes a function name as parameter and we can bypass the check for the function name by specifying a debug
parameter. This makes it trivial to achieve RCE specifying system
as hash_function
parameter, the command to execute as the password
parameter and adding a debug
parameter which can be anything.
hash.php
1 |
|
For this we first intercept the request with burp send it to repeater and set up our ncat listener.
1 |
|
Now we can send the payload, get a reverse shell back, upgrade it with python and fix the terminal size.
1 |
|
Password reuse
Checking the home folder there is a www-adm
user. Testing for password reuse we are able to change to it with the credentials www-adm:gameover
.
1 |
|
1 |
|
API
Checking the users home directory there is a .wgetrc
which contains a username and a password which hints at an api.
1 |
|
Checking a source file which seems to interact with the api we are able to identify a hostname and also a port.
1 |
|
Specifying our found credentials and curling /
on the api it seems /check_db
could be very interesting.
1 |
|
Testing this we are able to retrieve a lot of information which we can bring into a nicer form using jq
.
1 |
|
There is another database password for the user drew, which turns out to be also his ssh password.
1 |
|
Being logged in as drew we are able to grab the user flag.
1 |
|
Root
Docker restart
Looking around on the host there is an email to drew from game-adm. The email states there is a docker running with an active healthcheck. If the health check fails the docker will get restarted.
1 |
|
Looking at our ssh keys we seem to have the key pair for this container.
1 |
|
1 |
|
Checking the arp table reveals possible locations of the docker and checking it we are able to sucessfully ssh into it.
1 |
|
1 |
|
1 |
|
Looking at the entrypoint.sh script it executes all files in the /docker-entrypoint.d/
directory which seems to be mirrored from the host filesystem. We might be able to abuse this to get root on the docker by placing our script in there. However for this to work the docker needs to be restarted.
Checking for listening ports there is an application listening on port 9999
1 |
|
We can find the source for this is /usr/src/app/server.js
. The server runs a rock paper scissors games and allowes to autoplay for a specified number of round. The script sets the maximum amount of rounds to 100, but only checks if the value ever reaches 0. Specifying a negative value we should be able to generate an infinite loop, hang the container and fail the healthcheck.
server.js
1 |
|
For easier access we first set up a reverse portforward of the application to our machine using chisel.
1 |
|
1 |
|
Looking at the application in our webbrowser it is the one the source mentions.
We go over to the autoplay functionality and intercept the request in burp.
Next we open up another ssh connection and run an infinete loop on the host putting our script in place. The script simply copies bash
to /tmp
and gives it the suid bit.
1 |
|
Now we can send the request with the negative round count to crash the docker.
After a few moments the health check fails and we get logged out of the docker ssh connection.
1 |
|
Logging back in we see the suid bash
waiting for us in the /tmp
folder.(The ip of the container might change with the restart)
1 |
|
Now we can easily escalate to root in the docker.
1 |
|
Since the /docker-entrypoint.d/
in the container is mapped to /opt/docker-entrypoint.d/
on the host filesystem we can simply copy sh
into it and give it the suid bit.
1 |
|
This gives us root on the host and we can add the root flag to our collection.
1 |
|
Unintended
Until some time after release the box had some unintended ways to solve in the web part which shortened it quite a bit.
Register admin
You could simply register a user with the name admin
and then log into game bypassing the XSS and key verification step.
Abuse cookie reuse
There was also a vulnerability in the session cookies of the app in the container. Since both apps ran in the same container they shared the session cookies. With this you could simply replace the dev cookie with the game cookie and get access to dev.
Log poisoning
Once in dev it was possible to include files outside of the webroot with the file://
wrapper. Since this also includes php code you could get RCE by including files you can write php code to. One of those files are the access logs. The location could be retrieved by first including the 000-default.conf
of apache.
Checking the mentioned log path you were able to include it.
Next you had to poison the log with php code. Since the User-Agent
header is included in the log you could just exchange it for a small php webshell and make a request to the correct site. Generally you have to be cautios though to not have a syntax error because it makes the log unusable for further RCE.
Including the log again now with our parameter from the webshell we have RCE on the target.
We set up our listener and upon sending the request we recieve a shell back as www-data on the webserver container.
1 |
|
1 |
|
Session poisoning
Another way to get RCE from LFI in php is to poison the session. The session get’s usually stored in the format sess_{your PHPSESSID}
. The place for it can vary though, here it is in /tmp
. Since we have control over it’s content with e.g. the username we can simply change our username and include it. This is more reliable than log poisoning because we are able to correct syntax mistakes.
Updating the username to contain a small php webshell and refreshing the pages you could achieve RCE in the same way as with log poisoning.
1 |
|
1 |
|