2、MQTT安全威胁分析:中间人攻击、重放攻击、拒绝服务攻击、身份伪造
好,咱们接着聊。上一章我们把MQTT的基础握手流程和TLS的配置跑通了。但说实话,光配好证书,不代表你的系统就固若金汤了。
我在做物联网安全评估的时候,经常遇到客户问:“我用了TLS,是不是就万事大吉了?” 我的回答通常是:TLS只是第一道门,门后面还有一堆坑等着你。
这一章,我们就来拆解MQTT世界里最常见的四种攻击手法。你想想看,攻击者不会按套路出牌,他们专挑你最松懈的地方下手。
2.1 中间人攻击(Man-in-the-Middle, MITM)
中间人攻击,说白了就是“窃听+篡改”。攻击者悄悄站在客户端和Broker之间,把双方的数据包都截下来,自己看一遍,甚至改一改再发出去。
为什么MQTT容易中招?
因为MQTT默认走的是明文TCP(1883端口)。你想想看,一个温度传感器上报的数据,如果被中间人改成“-100°C”,你的制冷系统可能就炸了。
攻击场景还原:
客户端 → [攻击者] → Broker
攻击者可以:
- 窃取订阅的主题(比如“工厂/设备/密码”)
- 篡改发布的消息(比如把“阀门关闭”改成“阀门打开”)
- 伪造CONNECT报文,冒充合法设备
我的实战经验:
有一次帮客户做渗透测试,他们自信满满地说“我们内网很安全”。结果我用一个树莓派接在交换机上,跑了5分钟的tcpdump,就抓到了明文传输的MQTT登录凭证。嗯,那个客户的脸都绿了。
怎么防?
- 强制TLS: 别用1883端口了,老老实实走8883。我建议所有生产环境都开启TLS双向认证(mTLS)。
- 证书固定(Certificate Pinning): 在客户端代码里写死Broker的证书指纹。这样就算攻击者伪造了CA签发的证书,客户端也不认。
// 伪代码示例:证书固定
const expectedFingerprint = "A1:B2:C3:D4:E5:F6:...";
const serverCert = connection.getPeerCertificate();
if (sha256(serverCert.publicKey) !== expectedFingerprint) {
throw new Error("中间人攻击!证书不匹配");
}
2.2 重放攻击(Replay Attack)
重放攻击,就是攻击者把之前抓到的合法数据包,原封不动地再发一遍。你想想看,如果攻击者抓到了“打开车库门”的指令,半夜给你重放一遍……
MQTT为什么怕重放?
因为MQTT协议本身没有时间戳或序列号机制。一个PUBLISH报文,只要格式正确,Broker就认为是合法的。
我曾经踩过的坑:
有个智能门锁项目,用了MQTT控制开关。测试时发现,只要抓包抓到“unlock”指令,用任何工具重放一次,门就开了。后来我们不得不在Payload里加入时间戳和HMAC签名,才堵住这个漏洞。
防御方案:
- Payload加时间戳: 每条消息里带上毫秒级时间戳,Broker端判断是否在合理窗口内(比如±5秒)。
- 消息序列号: 每个设备维护一个递增的序列号,Broker记录最近N个序列号,重复的直接丢弃。
- 一次性Nonce: 每次通信前,客户端向Broker请求一个随机数,后续消息必须带上这个Nonce。
// 带时间戳和签名的Payload示例
{
"device_id": "sensor_01",
"value": 25.3,
"timestamp": 1699000000123,
"signature": "HMAC-SHA256(secret, device_id + value + timestamp)"
}
2.3 拒绝服务攻击(Denial of Service, DoS)
DoS攻击的目标很简单:让你的Broker瘫痪,所有设备都连不上。MQTT的Broker其实挺脆弱的,因为每个连接都要维持TCP状态、处理心跳、管理订阅树。
常见的MQTT DoS手法:
- 洪水攻击: 短时间内发起大量CONNECT请求,耗尽Broker的连接池。
- 大Payload攻击: 发送超大的PUBLISH报文(比如几十MB),撑爆Broker的内存。
- 订阅爆炸: 一个客户端订阅几万个主题,导致Broker的订阅树膨胀,CPU飙升。
- 遗嘱消息滥用: 恶意设置遗嘱消息,然后断线触发,让Broker不断转发垃圾消息。
真实案例:
我记得有个智慧农业项目,传感器每隔5秒上报一次数据。攻击者伪造了1000个设备同时连接,每个设备都订阅了“#”(通配符)。结果Broker的CPU直接100%,正常设备全部掉线。嗯,这就是典型的“订阅爆炸”。
怎么扛?
| 攻击类型 | 防御措施 |
|---|---|
| 连接洪水 | 限制单个IP的最大连接数;启用客户端ID白名单 |
| 大Payload | 在Broker端设置最大报文大小(比如256KB) |
| 订阅爆炸 | 限制每个客户端的订阅数量;禁用“#”通配符订阅 |
| 遗嘱滥用 | 限制遗嘱消息的大小和发送频率 |
我个人的习惯:
在生产环境里,我会在Broker前面加一层反向代理(比如Nginx或HAProxy),先做流量清洗和速率限制。这样就算被攻击,也是代理先扛,Broker相对安全。
2.4 身份伪造(Identity Spoofing)
身份伪造,就是攻击者冒充合法设备或用户,连接到Broker。MQTT的身份认证其实很弱——默认只靠一个Client ID和用户名/密码。
为什么容易伪造?
- Client ID可预测: 很多设备用MAC地址或序列号作为Client ID,攻击者扫一遍就能猜出来。
- 弱密码: 我见过太多设备用“admin/admin”或者“123456”了。
- 无证书校验: 如果只用TLS单向认证,服务器不验证客户端证书,那谁都能连。
我曾经遇到的一个奇葩案例:
有个客户的所有设备都用“device_001”、“device_002”这样的Client ID。攻击者写了个脚本,从1循环到10000,每个ID都尝试连接。结果你猜怎么着?有300多个设备根本没设密码,直接就连上了。攻击者可以随意发布消息,控制所有设备。
防御方案:
- 强认证: 使用TLS双向认证(mTLS),每个设备持有唯一的客户端证书。
- 动态Client ID: 不要用固定ID,用“设备类型+随机数+时间戳”组合。
- ACL访问控制: 在Broker上配置细粒度的权限,比如“设备A只能发布topic_A,不能订阅topic_B”。
- Token机制: 使用JWT或OAuth2.0,每次连接都验证Token的有效性。
# Mosquitto ACL示例
# 只允许设备sensor_01发布sensor/data主题
user sensor_01
topic write sensor/data
# 禁止所有设备订阅admin/#主题
pattern read admin/#
小结
这四种攻击,说白了就是利用了MQTT协议设计上的“信任过度”。协议本身假设网络是安全的、设备是可信的——但现实世界恰恰相反。
我个人建议,做物联网安全一定要有“零信任”思维:不信任网络,不信任设备,不信任任何人。每一层都加上防护,才能让你的MQTT系统真正扛得住攻击。
下一章,我们会深入TLS的配置细节,包括如何生成自签名证书、如何做双向认证。到时候我会分享一些我在项目中踩过的坑,保证让你少走弯路。