2023年11月,记录漏洞Apache ActiveMQ 曝出一个反序列化漏洞,记录漏洞编号 CVE-2023-46604。记录漏洞攻击者构造一个恶意的记录漏洞序列化对象,通过 OpenWire 协议发给 Broker,记录漏洞就能在目标机器上执行任意代码。记录漏洞这个漏洞的记录漏洞 CVSS 评分是 10.0——满分。更麻烦的记录漏洞是,它不需要任何认证。记录漏洞
我当时在一家小公司做后端维护,记录漏洞消息队列用的记录漏洞是 ActiveMQ 5.17.3。看到这条公告的记录漏洞时候,后背一阵发凉。记录漏洞我们的记录漏洞 Broker 直接暴露在内网,虽然理论上外网访问不到,记录漏洞但内网横向移动这种事,谁说得准呢?
修漏洞这事,听起来简单——升级版本就行了。但实际操作起来,远没有那么干脆。
先说升级。5.17.3 要升到 5.18.3 或者 5.17.6,版本号看着差不多,但中间隔了好几个小版本。ActiveMQ 的版本兼容性一向让人头疼,尤其是我们还在用 Spring Boot 2.x 集成,依赖树里有一堆传递依赖。我试着在测试环境升了一次,启动直接报 ClassNotFoundException,原因是某个 activemq-client 的包路径在新版本里改了。翻了一遍 release notes,发现是 5.18 引入的 Jakarta 命名空间迁移导致的。改依赖、改配置、改代码里的 import,折腾了大半天才跑通。
但真正让我头疼的不是升级本身,是升级之后的事。
我们有一个老系统,用的是 ActiveMQ 的 MessageListener 异步消费模式。升级到 5.18.3 之后,消费者开始偶尔丢消息。不是每次都丢,大概几百条里丢一两条。日志里没有任何异常,Broker 端显示消息已投递,消费端却说没收到。我盯着这个问题查了三天,最后在一个 GitHub issue 里找到了线索:新版本对 prefetch 策略做了调整,默认值从 1000 变成了 100,而我们的消费者处理逻辑里有一步是同步写数据库,处理慢的时候,prefetch 太小会导致消息在客户端缓冲区里排队,一旦连接抖动,未确认的消息就会重新入队——但我们的代码里有个 bug,在 onMessage 里捕获了异常却没重新抛出,导致消息被静默丢弃。
这个问题其实一直存在,只是旧版本的 prefetch 值大,掩盖了它。升级不过是把隐藏的雷给踩响了。
再说回漏洞本身。CVE-2023-46604 的利用方式其实不复杂:攻击者发一个 ExceptionResponse 类型的消息,里面的 Throwable 字段指向一个远程的 XML 配置文件,Broker 在反序列化时会去加载这个类,从而触发代码执行。POC 在 GitHub 上很快就有了,我扫了一眼,核心就是构造一个 org.springframework.context.support.ClassPathXmlApplicationContext 的序列化对象。
这让我意识到一件事:很多漏洞的根因不是代码写错了,而是设计上就不该把反序列化暴露给不可信来源。ActiveMQ 的 OpenWire 协议默认监听 61616 端口,只要这个端口可达,攻击面就存在。我们后来做的第一件事不是升级,而是在 Broker 前面加了一层网络策略,只允许特定的几个 IP 访问 61616。升级是第二周才完成的。
记录漏洞这件事,我的习惯是:不光记编号和修复版本,还要记清楚“为什么会有这个漏洞”“我们当时的配置是什么样的”“修复过程中踩了哪些坑”。因为下一次遇到类似问题,这些上下文比官方公告有用得多。
现在我的笔记里躺着十几条这样的记录。有些是已经修完的,有些是评估后决定不修的——比如某个只在特定配置下触发的 XSS,我们的场景根本用不到那个功能。不修也是一种决策,但得写清楚理由,不然过半年自己都忘了当时怎么想的。
漏洞这东西,修是修不完的。但每修一个,至少得弄明白它为什么存在。不然下次换个编号,还是一样的坑。