CVE-2023-46604分析
本文最后更新于:2024年8月14日 中午
CVE-2023-46604 Apache ActiveMQ RCE 漏洞分析
前置知识
消息队列——MessageQueue
我们可以把消息队列看作是一个存放消息的容器,当我们需要使用消息的时候,直接从容器中取出消息供自己使用即可。由于队列 Queue 是一种先进先出的数据结构,所以消费消息时也是按照顺序来消费的。
参与消息传递的双方称为 生产者 和 消费者 ,生产者负责发送消息,消费者负责处理消息。
操作系统中的进程通信的一种很重要的方式就是消息队列。我们这里提到的消息队列稍微有点区别,更多指的是各个服务以及系统内部各个组件/模块之前的通信,属于一种中间件 。
通常来说,使用消息队列主要能为我们的系统带来下面三点好处:
- 异步处理
- 削峰/限流
- 降低系统耦合性
除了这三点之外,消息队列还有其他的一些应用场景,例如实现分布式事务、顺序保证和数据流处理。
消息队列使用发布-订阅模式工作,消息发送者(生产者)发布消息,一个或多个消息接受者(消费者)订阅消息。 从上图可以看到消息发送者(生产者)和消息接受者(消费者)之间没有直接耦合,消息发送者将消息发送至分布式消息队列即结束对消息的处理,消息接受者从分布式消息队列获取该消息后进行后续处理,并不需要知道该消息从何而来。对新增业务,只要对该类消息感兴趣,即可订阅该消息,对原有系统和业务没有任何影响,从而实现网站业务的可扩展性设计。
ActiveMQ
ActiveMQ是Apache软件基金下的一个开源软件,它遵循JMS1.1规范(Java Message Service),是消息队列服务,是面向消息中间件(MOM)的最终实现,它为企业消息传递提供高可用、出色性能、可扩展、稳定和安全保障。
在ActiveMQ中,生产者(Producer)发送消息到Queue或者Topic中,消费者(consumer)通过ActiveMQ支持的传输协议连接到ActiveMQ接受消息并做处理。
环境准备
影响版本
Apache ActiveMQ < 5.18.3
Apache ActiveMQ < 5.17.6
Apache ActiveMQ < 5.16.7
Apache ActiveMQ < 5.15.16
环境搭建
安装ActiveMQ存在漏洞的版本,我这里以5.17.3为例
下载解压后,在bin文件夹下,使用./activemq.bat start
启动服务,注意需要Java>=11
创建Maven项目,配置依赖
1 |
|
在依赖里找到activemq-client里面的BaseDataStreamMarshaller,下载源码,此时这个包里的所有文件都是Java源码便于我们分析
调试分析
首先,我先给出漏洞存在的代码,在org/apache/activemq/openwire/v9/BaseDataStreamMarshaller.java
中,它存在一个createThrowable
的函数,如下:
可以看到,这里使用了反射,根据我们传入的类名生成了它的一个构造器对象,最后返回了这个类的一个实例。很显然,这里可以调用任意类造成RCE。
我们查看对createThrowable
的调用,只有两处并且都在同一个类下,tightUnmarsalThrowable
和looseUnmarsalThrowable
,分别跳转过去看一眼。
我们可以看到,在looseUnmarsalThrowable
中,调用了looseUnmarsalString
对我们的传入进行处理,可以看到这里面只是把我们的输入转成Unicode字符集就直接返回了。而在另一个tightUnmarsalThrowable
函数中,做了很多别的处理,可以自己去看看。
1 |
|
接着,查看对looseUnmarsalThrowable
的调用,有好几个,那么我们重点关注了ExceptionResponseMarshaller
这个类(当然其他的也可以),它是BaseDataStreamMarshaller
的一个子类,按照名字来说就是对ExceptionResponse进行序列化处理的。
跳转到这个调用处,可以看到在这里面将o强转为ExceptionResponse
类,然后进行反序列化
反向分析就到这里,因为对于looseUnmarshal
的调用太多了,我们现在还不确定在ActiveMQ中反序列化的具体流程,但是没有太大关系,目前很清晰的知道,只需要构造一个ExceptionResponse
类的序列化数据,传递给ActiveMQ中进行反序列化,最终可以触发到createThrowable
函数导致RCE
到目前我们的链子为:
1 |
|
下面远程调试ActiveMQ,我们先编写一个测试类,用于发送OpenWire格式的数据。
这个相当于是生产者Client在向ActiveMQ服务端发送消息
1 |
|
注意接下来的调试是远程调试ActiveMQ,通过OpenWire通信的包会在org.apache.activemq.openwire
的doUnmarshal
函数中进行反序列化,这里需要DataType为31才会触发我们需要的反序列化函数
进一步处理,
下面的问题转移到了如何构造一个ExceptionResponse
的数据,接下来回到客户端的调试
在数据进入到序列化函数之前,会进入到一个叫oneway
的函数,这里学习到了一个patch手法,在当前源文件的目录下新建一个和依赖类相同路径的文件,根据classpath的默认调用顺序,会优先进入这个新的同名类中
怎么做?
在当前源码目录下新建一个 org.apache.activemq.transport.tcp.TcpTransport
类, 然后重写对应的逻辑, 这样在运行的时候, 因为 classpath 查找顺序的问题, 程序就会优先使用当前源码目录里的 TcpTransport 类。
1 |
|
然后是 createThrowable 方法的利用, 因为 ActiveMQ 自带 spring 相关依赖, 那么就可以利用 ClassPathXmlApplicationContext 加载 XML实现 RCE
在 marshal 的时候会调用 o.getClass().getName()
获取类名, 而 getClass 方法无法重写 (final), 所以我在这里同样 patch 了 org.springframework.context.support.ClassPathXmlApplicationContext
, 使其继承 Throwable 类
最后成功触发
编写Exp
上面的分析都是基于ActiveMQ提供的client来进行发送恶意数据进行RCE,但是其实可以直接构造OpenWire协议的流量发送给ActiveMQ进行解析,从而不用进行patch操作。
也就是大概三部分:size、type、data,同时也可以在官方解释的下文发现,type=31正好就是我们需要的ExceptionResponse
然后数据部分也就是官方说的command-specific-fields
部分的要求,对于string类型的数据采用UTF-8进行编码
通过wireshark抓包分析数据包具体格式,还需要一些其他字段
最后构造Exp:
1 |
|
漏洞修复
BaseDataStreamMarshaller
类加入了validateIsThrowable
方法,判断我们传入的类是否合法
1 |
|