MQTT over TLS Analysis

practical

MQTT over TLS Analysis

Beaucoup d'opérateurs pensent que basculer MQTT sur le port 8883 (TLS) suffit à sécuriser leur flotte. C'est faux. Une mauvaise validation de certificat, un CA auto-signé largement distribué, ou un client mobile qui accepte n'importe quoi transforme TLS en théâtre.

Avertissement légal : ces techniques ne doivent être appliquées que sur vos propres infrastructures ou dans le cadre d'un mandat de test d'intrusion signé.

Connexion TLS propre

# Vérification stricte CA + hostname
mosquitto_sub -h broker.cible.tld -p 8883 \
  --cafile /etc/ssl/certs/ca-certificates.crt \
  -t '#' -v

# Mutual TLS : le client présente aussi un certificat
mosquitto_sub -h broker.cible.tld -p 8883 \
  --cafile ca.crt --cert client.crt --key client.key \
  -t 'home/+/temp' -v

Si la connexion échoue, lancez openssl s_client :

openssl s_client -connect broker.cible.tld:8883 -showcerts </dev/null

Notez le CN, le SAN, l'émetteur. CN = localhost, mqtt, ou IP interne ? Première trouvaille : certificat non adapté.

Détection de validation laxiste

Le bug classique : un client (firmware ESP32, app mobile, gateway industrielle) accepte n'importe quel certificat.

# Générez un CA et un cert serveur en 30 secondes
openssl req -new -x509 -days 365 -nodes -out ca.crt -keyout ca.key \
  -subj "/CN=FakeCA"
openssl req -new -nodes -out server.csr -keyout server.key \
  -subj "/CN=broker.cible.tld"
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key \
  -CAcreateserial -out server.crt -days 365

# Lancez mosquitto en TLS sur 8883
cat > rogue.conf <<'EOF'
listener 8883
cafile ca.crt
certfile server.crt
keyfile server.key
require_certificate false
allow_anonymous true
EOF
mosquitto -c rogue.conf -v

Redirigez le trafic du device (ARP spoof, DNS poisoning, DHCP en lab). Si le device publie chez vous, la validation TLS est inexistante.

Interception avec mitmproxy

# Extrayez le CA bundle du firmware
binwalk -e firmware.bin
grep -rE "BEGIN CERTIFICATE" _firmware.bin.extracted/

# Si le CA est court, vérifiez s'il est public ou custom
openssl x509 -in extracted_ca.pem -text -noout | grep -E "Issuer|Subject"

Si le firmware embarque un CA custom et que vous récupérez sa clé privée (cas réel — vendors distribuant la même paire à tous les clients), vous signez votre propre cert serveur et l'interception devient triviale.

Inspection Wireshark

# Filtre Wireshark utile
tls.handshake.type == 1 and tcp.port == 8883

Le SNI révèle le hostname réel du broker même si le device utilise une IP. Combiné avec la taille des messages chiffrés, vous devinez les patterns (un message de 47 octets toutes les 30s = keepalive avec température).

Script Python : test de connexion TLS faible

import paho.mqtt.client as mqtt
import ssl

def test_broker(host, port=8883):
    client = mqtt.Client()
    ctx = ssl.create_default_context()
    ctx.check_hostname = False
    ctx.verify_mode = ssl.CERT_NONE
    client.tls_set_context(ctx)
    try:
        client.connect(host, port, 5)
        client.loop_start()
        return "ACCEPTE TLS sans validation"
    except Exception as e:
        return f"refuse: {e}"
    finally:
        client.disconnect()

for h in ["broker1.tld", "broker2.tld"]:
    print(h, "->", test_broker(h))

Checklist de findings

  • Certificat avec CN incorrect ou expiré
  • TLS 1.0 / 1.1 activé sur 8883
  • require_certificate false côté broker
  • CA partagé entre tous les déploiements du fabricant
  • Clients qui ignorent verify_mode
  • Port 1883 (non chiffré) toujours ouvert en parallèle