- 物联网
- 消息队列
- 后端
- 网络/通信
【免费下载链接】mosquitto
Eclipse Mosquitto - An open source MQTT broker
本篇文章以 Eclipse Mosquitto 官方安全公告(security-advisory-cve-2017-7650)为骨架,结合当前仓库源码(
src/security_default.c、src/plugin_acl_check.c、lib/util_topic.c)与配置示例(aclfile.example、mosquitto.conf),完整还原 CVE-2017-7650 的成因、危害、修复方案及当前实现中的纵深防护逻辑。读者读完后,将能准确理解 Mosquitto Pattern 型 ACL 的匹配机制,识别并规避同类绕过风险,并掌握 1.4.12 修复版本中针对危险用户名/客户端 ID 的访问限制策略。
一、漏洞总览:什么是 CVE-2017-7650
CVE-2017-7650 是 Mosquitto 历史上一个与**基于模式(Pattern)的访问控制列表(ACL)**相关的安全漏洞,影响0.15 至 1.4.11(含)的所有版本。
漏洞的核心危害在于:客户端将自身的 username(用户名)或 client id(客户端标识)设置为#或+时,可以绕过 Pattern 型 ACL 的限制,从而访问本无权访问的 MQTT 主题。由于 MQTT 的#与+分别是"多层通配符"与"单层通配符",它们在主题过滤器中具有特殊语义,而当它们出现在 username / client id 中并被拼入 Pattern ACL 的检查路径时,会破坏 ACL 匹配的预期语义,导致权限判定被绕过。
公告同时指出:同样的缺陷可能存在于第三方认证/访问控制插件中(即使用 Mosquitto 插件接口实现 ACL 判断的插件,若直接以%c/%u方式拼装主题后再做通配匹配,同样面临被#/+注入的风险)。
漏洞仅在使用Pattern 型 ACL(或可能使用第三方插件)的部署场景下才会实际触发;不使用 Pattern ACL 的默认配置不受影响。该漏洞由 HackerDom CTF 战队的 Artem Zinenko 发现并负责任地报告,已在Mosquitto 1.4.12中修复,官方同时为旧版本提供了补丁。
二、前置知识:Mosquitto 的 ACL 与 Pattern 型 ACL
要理解漏洞,先要理解 Mosquitto 的访问控制体系。Mosquitto 的 ACL 定义在acl_file指定的文件中(对应配置项acl_file),支持三种粒度:
- 匿名/全局主题规则:文件开头的
topic行,作用于所有匿名客户端; - 用户专属规则:
user <username>行之后的topic行,只作用于指定用户; - Pattern(模式替换)规则:
pattern行,使用%c(客户端 ID)与%u(用户名)做占位符,对所有客户端生效。
官方示例文件 aclfile.example 完整展示了这三种写法:
# This affects access control for clients with no username. topic read $SYS/# # This only affects clients with username "roger". user roger topic foo/bar # This affects all clients. pattern write $SYS/broker/connection/%c/state其中最后一行pattern write $SYS/broker/connection/%c/state的含义是:允许任意客户端(以其 client id 替换%c)向$SYS/broker/connection/<client-id>/state主题写入连接状态消息——这也是官方推荐的 bridge 连接消息授权写法。
Pattern 语法的官方约束
mosquitto.conf 中对 Pattern ACL 的语法做了完整说明:
- 可用占位符:
%c匹配客户端 ID,%u匹配用户名; - 替换占位符必须是主题层级中唯一的文本(即
%c/%u必须独占一个 topic level,不能与其它字符拼接,如foo%c属于非法用法); - 语法形式与
topic关键字相同,只是关键字换成pattern:pattern [read|write|readwrite] <topic>- 示例:
pattern write sensor/%u/data(允许各用户向自己的sensor/<username>/data写数据)
- Pattern ACL 对所有用户生效,即使前面已经定义了
user规则; deny型规则(topic ... deny或pattern ... deny)优先于放行规则处理。
在源码层面,Pattern 规则的解析与存储由 src/security_default.c 中的add__acl_pattern()完成:函数会统计该 pattern 中包含的%c(acl->ccount)与%u(acl->ucount)出现次数,用于后续匹配时判断是否需要替换;若一条 pattern 既不含%c也不含%u,会打印警告日志。同时,deny(MOSQ_ACL_NONE)型 ACL 会被插入到放行型 ACL 链表头部,保证"先否定、后放行"的判定顺序。
三、漏洞原理深度剖析:#与+如何绕过 Pattern ACL
3.1 匹配替换机制
Pattern ACL 的匹配逻辑位于 src/security_default.c 的mosquitto_acl_check_default()中。核心调用是:
mosquitto_topic_matches_sub_with_pattern(acl_root->topic, ed->topic, ed->client->id, ed->client->username, &result);该函数声明于 lib/util_topic.c,实为topic_matches_sub(sub, topic, clientid, username, true, result)的封装:先把 pattern 中的%c/%u替换为客户端真实的 client id / username,再按 MQTT 主题通配规则(+单层、#多层)进行匹配。
3.2 绕过路径:通配符注入
问题出在替换后的匹配阶段。当客户端将 username 或 client id 设置为#或+时:
#作为 username/client id:替换进 pattern 后,形如sensor/%u/data会变成sensor/#/data——在 MQTT 主题语义中#是"匹配剩余所有层级"的多层通配符。虽然sensor/#/data这种"#不在末级"的写法在标准订阅里不合法,但基于字符串的匹配实现中,#一旦被注入就可能使检查路径产生非预期的"匹配成功"结果,从而让客户端获得它本不该拥有的读/写权限。+作为 username/client id:同理,+被替换进 pattern 后充当"匹配任意单层"的通配符,使客户端得以命中任何符合该层级结构的主题。
因此,攻击者只需要把 MQTT CONNECT 报文中的 username 或 client id 直接写成#/+,就能让 Pattern ACL 的权限判定"自我放行",实现本地或远程的越权访问。公告原话是:这类客户端可以访问它们本没有权限访问的 MQTT 主题。
3.3 影响面判定:仅限 Pattern 与插件场景
从源码可以精确确认漏洞的触发前提。在mosquitto_acl_check_default()中:
- 若
acl_file、内存 ACL 列表、pattern 列表三者皆为空,直接返回MOSQ_ERR_PLUGIN_IGNORE(src/security_default.c); - 仅当
security_opts->acl_patterns非空时,才会进入危险字符检查与 pattern 匹配循环(src/security_default.c)。
这印证了公告的结论:只有启用 Pattern 型 ACL,或使用存在同类问题的第三方认证/访问控制插件时,漏洞才实际生效。纯topic字面量 ACL(含+/#通配符的静态规则)不受此漏洞影响——因为静态规则中的通配符是管理员明确写入的,不存在客户端可控的注入面。
此外还有一个值得注意的细节:从当前源码看,mosquitto_acl_check_default()对MOSQ_ACL_SUBSCRIBE/MOSQ_ACL_UNSUBSCRIBE直接返回成功(src/security_default.c),即订阅/退订请求不经过默认 ACL 的 pattern 检查,漏洞风险主要集中于**发布(write)与读取(read)**路径。
3.4 完整调用链
消息收发路径上的 ACL 判定统一收敛到 src/plugin_acl_check.c 的mosquitto_acl_check(),其调用链如下:
- 发布路径:src/handle_publish.c 以
MOSQ_ACL_WRITE调用; - 订阅路径:src/handle_subscribe.c 以
MOSQ_ACL_SUBSCRIBE调用; - 退订路径:src/handle_unsubscribe.c 以
MOSQ_ACL_UNSUBSCRIBE调用; - 投递/保留消息读取路径:src/database.c、src/retain.c、src/subs.c 以
MOSQ_ACL_READ调用; - 持久化恢复场景:src/context.c。
mosquitto_acl_check()的判定顺序是:先执行插件的 ACL 检查回调(plugin__acl_check),再执行内置的默认 ACL 检查(mosquitto_acl_check_default),并处理MOSQ_ERR_PLUGIN_IGNORE(视为插件不存在)与MOSQ_ERR_PLUGIN_DEFER(延迟判定,最终按拒绝处理)两种特殊返回码。这意味着:即便内置 ACL 已修复,第三方插件若直接用%c/%u拼接后做通配匹配,仍可能单独存在同类漏洞——这正是公告特别提醒"同一问题可能存在于第三方插件"的原因。
四、官方修复方案(1.4.12):对危险用户名/客户端 ID 的访问限制
4.1 修复策略
Mosquitto 1.4.12 的修复思路是从源头切断通配符注入:对 username 或 client id 中包含#、+的客户端,直接拒绝其收发任何受 Pattern ACL 或插件检查的消息。公告明确列出的修复要点如下:
- 修复针对客户端 username / client id 中含
#、+的访问限制问题; /(斜杠)也被一并列入禁用字符,因为它在主题中同样具有层级分隔的特殊语义,可能构成额外风险;- 受限制的客户端将不能接收或发送任何受 Pattern ACL 检查的消息,也不能收发任何受插件检查的消息。
4.2 当前仓库源码中的防护实现
虽然本仓库为后续演进版本,但防护逻辑清晰保留在 src/security_default.c 的mosquitto_acl_check_default()中。在进入 pattern 匹配循环之前,源码先做危险字符检查:
if(ed->client->username && strpbrk(ed->client->username, "+#")){ log__printf(NULL, MOSQ_LOG_NOTICE, "ACL denying access to client with dangerous username \"%s\"", ed->client->username); return MOSQ_ERR_ACL_DENIED; } if(ed->client->id && strpbrk(ed->client->id, "+#")){ log__printf(NULL, MOSQ_LOG_NOTICE, "ACL denying access to client with dangerous client id \"%s\"", ed->client->id); return MOSQ_ERR_ACL_DENIED; }这段代码的语义要点:
- 检查对象是username 与 client id 两者,任一带
+或#即拒绝; - 检查仅发生在 pattern ACL 存在时(外层有
if(acl_root){...}包裹),与公告"仅 Pattern 场景受影响"的定位一致; - 拒绝时打印
MOSQ_LOG_NOTICE级别日志,便于运维人员发现恶意尝试; - 该检查位于
mosquitto_acl_check_default()内部,因此仅约束内置 ACL;插件侧仍需插件自身处理(MOSQ_ERR_PLUGIN_DEFER/MOSQ_ERR_PLUGIN_IGNORE语义详见 src/plugin_acl_check.c)。
需要说明的是:当前仓库实现中以"+#"作为危险字符集合,而 1.4.12 公告中同时提及将/列入禁用列表——两处表述存在差异,若你的部署依赖旧版行为,请以对应版本的实际源码为准。无论采用哪个字符集合,核心原则一致:任何可能被替换进 pattern 并充当通配符的字符,都必须从客户端可控字段中剔除。
4.3 插件侧的修复义务
公告特别强调"同一问题可能存在于第三方认证/访问控制插件"。从调用链看,src/plugin_acl_check.c 中插件回调先于内置 ACL 执行,且插件返回的非IGNORE/DEFER结果会直接作为最终裁决。因此:
- 若你的插件自行实现了
%c/%u替换与通配匹配,必须同样校验 username / client id 中的危险字符; - 若插件只是转发给内置 ACL(返回
MOSQ_ERR_PLUGIN_IGNORE或MOSQ_ERR_PLUGIN_DEFER),则 1.4.12 的内置修复已足够覆盖。
五、1.4.12 版本完整修复清单
除 CVE-2017-7650 外,公告还列出了 1.4.12 在 Broker 侧修复的其它问题,供升级决策参考:
- 修复因客户端消息在无存储消息的情况下被持久化,导致
mosquitto.db损坏的问题; - 修复 bridge 无法正确重启的问题;
- 修复 Windows 平台
gets_quiet中的未初始化内存问题; - 修复非 glibc 系统上
WITH_ADNS=no的构建问题; - 修正 README 文档;
- 修复 OpenSSL 1.1 的弃用警告;
- 修复重复 bridge 名称导致的段错误(segfault)问题;
- 修复 CVE-2017-7650。
六、安全建议与缓解措施
结合公告与源码,给出如下实操建议:
- 立即升级:若使用 0.15–1.4.11 且启用了 Pattern ACL 或第三方 ACL 插件,应升级至 1.4.12 或更高版本;无法升级的旧版本部署,可获取官方为旧版提供的补丁。
- 检查配置面:确认
acl_file中是否使用了pattern关键字(mosquitto.conf 提供了完整的 pattern 语法与示例),并审视第三方插件的 ACL 实现是否直接拼接%c/%u后做通配匹配。 - 启用日志监控:升级后,针对"dangerous username/client id"的
NOTICE日志(src/security_default.c)可帮助识别正在尝试注入通配符的恶意客户端,建议接入日志告警。 - 纵深防御:Pattern 规则本身遵循"deny 优先于 allow"的链表排序(src/security_default.c),建议在敏感主题上显式添加
deny规则,作为字符校验之外的兜底。 - 遵循占位符规范:编写 pattern 时务必让
%c/%u独占一个主题层级(如sensor/%u/data),避免任何可能导致通配符与字面量混淆的拼接写法。
七、总结
CVE-2017-7650 是 MQTT 通配符语义与 ACL 模式替换机制相互作用下产生的典型注入类漏洞:#/+既可以是合法的客户端标识字符,也可以在 pattern 匹配时被解释为主题通配符。Mosquitto 的修复通过禁止危险字符出现在 username / client id 中,从根上消除了注入面;而当前仓库 src/security_default.c 中保留的防护代码,正是这一安全决策在长期演进中的延续。理解这一漏洞,对自行开发 MQTT 访问控制插件、或审计 Broker ACL 配置的工程师,都具有直接的参考价值。
- 物联网
- 消息队列
- 后端
- 网络/通信
【免费下载链接】mosquitto
Eclipse Mosquitto - An open source MQTT broker
相关推荐
Eclipse Mosquitto CVE-2017-7650 安全公告解读:pattern ACL 绕过漏洞的成因与修复方案
Eclipse Mosquitto CVE 2017 7650 安全公告解读:pattern ACL 绕过漏洞的成因与修复方案 导读 本文围绕 Mosquitt
后端消息队列消息路由Eclipse Mosquitto 安全公告解读:CVE-2017-7651 与 CVE-2017-7652 的漏洞原理、修复机制与 1.4.15 加固实践
Eclipse Mosquitto 安全公告解读:CVE 2017 7651 与 CVE 2017 7652 的漏洞原理、修复机制与 1.4.15 加固实践 本
后端消息队列消息路由Paddle 安全公告深度解析:paddle.topk 浮点异常漏洞(CVE-2023-52305)的原理与修复
Paddle 安全公告深度解析:paddle.topk 浮点异常漏洞(CVE 2023 52305)的原理与修复 导读 本文基于 Paddle(飞桨)核心框架安
人工智能深度学习机器学习大模型分布式训练预训练
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考