MQTT 安全加固实战:Mosquitto 认证配置

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 主题,不留例外。