MQTT 安全是物联网系统最容易被忽视的一环。大量 MQTT Broker 因为默认允许匿名接入、使用明文 1883 端口、缺乏主题级访问控制,等于把自己的设备控制权交了出去——任何人都能订阅 # 通配符读到全部传感器数据,甚至向控制主题发布指令。本文以 Mosquitto 为例,给出从关闭匿名到 TLS 加密的完整加固配置。
适用场景
- 自建 Mosquitto / EMQX 等 MQTT Broker,供 IoT 设备接入
- Broker 暴露在公网,或内网存在不可信主机
- 需要满足等保对“设备接入认证”和“传输加密”的要求
前置条件
- Mosquitto 2.0 及以上(2.0 起默认已禁止匿名)
- Linux 服务器 + sudo 权限,已放行 8883(TLS)端口
- 已准备服务器证书与私钥(自签或 CA 签发均可)
原理说明
MQTT 的三大风险点是接入、传输、授权。接入层:默认配置允许匿名(allow_anonymous true),任何客户端无需凭证即可连接;传输层:1883 明文,账号密码与载荷可被中间人直接读取;授权层:若无 ACL,任何已连接客户端都能用 # 或 + 通配符订阅/发布任意主题,一个被攻陷的低权限设备就能操控全局。
加固遵循“关闭匿名 + 强制认证 + 加密传输 + 主题最小权限”四步,缺一步防护体系就不完整。
MQTT 凭借轻量、省电、低带宽的特性,成为智能家居、工业物联网、车联网设备接入的事实标准,但也正因“为受限设备设计”,它的默认配置天然偏向“能连上”而非“连得安全”。在公网上暴露而未加固的 Broker,几乎是攻击者最省力的入口之一。
被忽视的遗嘱消息与保留消息
MQTT 还有两个常被忽略的风险点。一是遗嘱消息(Will Message):客户端异常断开时由 Broker 代发的预置消息,若遗嘱主题未做 ACL 限制,可被用来触发下游误动作。二是保留消息(Retained Message):Broker 会为每个主题保存最后一条 retain 消息,新订阅者一接入立即收到;如果保留消息里留有敏感状态(如门锁状态、GPS 坐标),就形成了持续泄露。
此外,若同时保留 listener 1883 明文端口,等于留了一条旁路。加固时必须只保留 8883,或在防火墙层直接封禁 1883。
操作步骤
第一步:关闭匿名接入
# /etc/mosquitto/conf.d/security.conf
allow_anonymous false
password_file /etc/mosquitto/passwd
第二步:创建认证账号
# 创建第一个账号(-c 会覆盖已有文件,后续用 -b 追加)
sudo mosquitto_passwd -c /etc/mosquitto/passwd device01
# 追加更多设备账号
sudo mosquitto_passwd -b /etc/mosquitto/passwd device02 'Str0ng-Pass!'
sudo chmod 600 /etc/mosquitto/passwd
sudo chown mosquitto:mosquitto /etc/mosquitto/passwd
第三步:配置主题级 ACL
# /etc/mosquitto/acl
# 设备只允许读写自己的主题
user device01
topic readwrite sensors/device01/#
# 后台只读订阅,不能下发指令
user backend
topic read sensors/#
topic write cmd/#
在配置中引用:acl_file /etc/mosquitto/acl。ACL 是防越权的核心,通配符必须限定在账号自己的命名空间内。
第四步:强制 TLS 加密
listener 8883
cafile /etc/mosquitto/ca.crt
certfile /etc/mosquitto/server.crt
keyfile /etc/mosquitto/server.key
tls_version tlsv1.2
# 如需双向认证(设备也带证书)
require_certificate true
use_identity_as_username true
第五步:重载并验证
sudo systemctl restart mosquitto
# 匿名连接应被拒绝
mosquitto_sub -h localhost -p 8883 --cafile ca.crt -t 'sensors/#' -C 1
# 带凭证 + TLS 应成功
mosquitto_sub -h localhost -p 8883 --cafile ca.crt \
-u device01 -P 'Str0ng-Pass!' -t 'sensors/device01/#' -C 1
第六步:限制明文端口与保留消息
# 只保留 TLS 监听,注释掉默认的 1883
# listener 1883
retain_available true
max_keepalive 120
# 通过防火墙兜底封禁明文端口
sudo firewall-cmd --permanent --remove-port=1883/tcp
sudo firewall-cmd --reload
第七步:开启认证失败日志与自动封禁
# /etc/mosquitto/conf.d/security.conf
log_dest file /var/log/mosquitto/mosquitto.log
log_type all
connection_messages true
配合 fail2ban 监控日志中的 Connection Refused: not authorised,对高频失败 IP 自动封禁,可有效压制针对 Broker 的暴力破解。
配置验证
- 匿名订阅返回
Connection Refused: not authorised即加固生效 - 用 device01 账号尝试订阅
sensors/device02/#,应被 ACL 拒绝 - 用
openssl s_client -connect host:8883确认走的是加密通道 - 检查
/var/log/mosquitto/mosquitto.log能记录到连接与认证事件
进阶:用客户端证书做设备身份
密码认证适合数量少、可人工维护的账号;当设备规模上千,逐个分发密码既不安全也难轮换,此时应改用客户端证书:每台设备签发唯一证书,Broker 端启用 require_certificate true 与 use_identity_as_username true,把证书 CN 直接映射为 MQTT 用户名,与 ACL 天然打通。证书还支持吊销(CRL)与有效期管理,是规模化设备接入的主流方案。
# 为设备签发客户端证书
openssl req -newkey rsa:2048 -nodes -keyout device01.key \
-out device01.csr -subj "/CN=device01"
openssl x509 -req -in device01.csr -CA ca.crt -CAkey ca.key \
-CAcreateserial -out device01.crt -days 365 -sha256
# Broker 侧启用吊销列表
# crlfile /etc/mosquitto/ca.crl
容量与稳定性加固
应对异常连接与资源耗尽,还应对连接数与消息速率做限制:
max_connections -1
max_queued_messages 1000
message_size_limit 262144
max_inflight_messages 20
persistent_client_expiration 7d
加固清单速查
为便于运维落地,把上述配置整理为一张上线前逐项打钩的检查表:
- 匿名接入已关闭(
allow_anonymous false) - 密码文件权限 600、属主为 mosquitto
- 每台设备独立账号,禁止共用
- ACL 已限定主题命名空间,禁绝裸
# - 仅监听 8883,1883 已封禁
- TLS 版本不低于 1.2,证书未过期
- 认证失败日志已开启并接入 fail2ban
- 管理账号与业务账号分离
这张表本身也可以作为等保测评时的自查证据,把“做没做”变成“有据可查”。
常见问题
FAQ 1:设备算力弱,跑 TLS 会不会太吃力?
对于 MCU 级设备,可只做客户端证书的轻量 TLS(如 mbedTLS),或让设备连内网网关、由网关统一与 Broker 做 TLS。万不得已的折中是内网用 1883 + VPN 隧道承载,但绝不允许把 1883 明文暴露在公网。
FAQ 2:设备掉线后重连频繁失败怎么办?
常见原因是 client_id 冲突导致 Broker 顶号,或密码文件权限不对(mosquitto 用户无读取权限)。建议为每台设备分配唯一 client_id,并检查 passwd 文件属主。若用证书认证,还需确认设备时间同步,避免证书有效期校验失败。
FAQ 3:多台 Broker 做桥接(bridge)时如何保证安全?
桥接连接本质上也是 MQTT 客户端,必须为桥接单独配置账号与 ACL,并强制走 TLS(bridge_cafile / bridge_certfile)。切忌使用管理账号做桥接,也不要在桥接配置里使用 topic # both 这类全量双向同步,否则一个分支被攻陷就会横向污染其他节点。
总结
MQTT 加固不复杂,难在坚持四步都做全:关闭匿名、强制认证、加密传输、主题最小权限。很多事故的根源就是“先用着 1883 明文,以后再说”。建议把这份配置作为设备接入的标准基线,新设备上线一律走 8883 + 独立账号 + 专属 ACL 主题,不留例外。