Apache ActiveMQ (版本 < 5.18.3) RCE 分析
Apache ActiveMQ (版本 < 5.18.3) RCE 分析
中午吃饭的时候看到赛博昆仑发了 Apache ActiveMQ 的漏洞通告, 于是去翻了翻 github commit
第一眼看感觉跟之前那个 spring-amqp 的漏洞差不多就偷个懒没复现, 后来发现大伙都在看这个洞
下午本来想摸鱼的结果被 @Y4er 叫起来让我讲讲这个 rce
自己之前没怎么接触过 ActiveMQ, 不过好在最后花了一点时间复现了出来, 简单记录一下分析的思路
参考:
https://github.com/apache/activemq/commit/958330df26cf3d5cdb63905dc2c6882e98781d8f
根据 diff 可以发现新版本为 BaseDataStreamMarshaller 类加入了 validateIsThrowable 方法
|
|
然后经过一些简单的搜索可以发现 ExceptionResponseMarshaller 这个类, 它是 BaseDataStreamMarshaller 的子类
其 tightUnmarshal/looseUnmarshal 方法会调用 tightMarshalThrowable/looseMarshalThrowable, 最终调用到 BaseDataStreamMarshaller 的 createThrowable 方法, 后者可以调用任意类的带有一个 String 参数的构造方法
ExceptionResponseMarshaller 顾名思义就是对 ExceptionResponse 进行序列化/反序列化的类
ExceptionResponse 的定义如下
|
|
回到上面的 BaseDataStreamMarshaller
有若干 Marshal/unmarshal 方法
这里以 tightUnmarsalThrowable 为例
方法内部会获取 clazz 和 message, 然后调用 createThrowable
找到对应的 tightMarshalThrowable
o 就是 ExceptionResponse 里面的 exception 字段 (继承了 Throwable), 然后分别将 o 的 className 和 message 写入序列化流
到这里思路其实已经差不多了, 我们只需要构造一个 ExceptionResponse 然后发给 ActiveMQ 服务器, 之后 ActiveMQ 会自己调用 unmarshal, 最后触发 createThrowable
下面只需要关注如何发送 ExceptionResponse 即可
参考网上的入门教程写了一个 demo
|
|
然后随便打几个断点试试 (注意在一次通信的过程中 ActiveMQ 会 marshal / unmarshal 一些其它的数据, 调试的时候记得判断)
org.apache.activemq.openwire.OpenWireFormat#doUnmarshal()
先获取 dataType, 然后根据它的值去 this.dataMarshallers 里面获取对应的序列化器
这个 dataType 其实对应的就是 Message 类内部的 DATA_STRUCTURE_TYPE
字段
在 demo 中我们发送的是一个 ObjectMessage (ActiveMQObjectMessage) 对象, 它的 dataType 是 26
而 ExceptionResponse 的 dataType 是 31, 对应上图中的 ExceptionResponseMarshaller
获取到了对应的序列化器之后, 会调用它的 tightUnmarshal / looseUnmarshal 方法进一步处理 Message 内容
我一开始的思路是去修改 ObjectMessage 的 DATA_STRUCTURE_TYPE
字段, 把它改成 31 然后发送
后来想了一会发现不能这么搞, 因为对于不同的 Message 类型, 序列化器会单独进行处理, 比如调用 writeXXX 和 readXXX 的类型和次数都不一样
因为 ExceptionResponseMarshaller 也有 marshal 方法, 所以思路就改成了如何去发送一个经由这个 marshaller 处理的 ExceptionResponse
后面开始调试 client 发数据的过程
给 OpenWireFormat 的 marshal 系列方法打个断点
调用栈往前翻可以找到 TcpTransport 这个类
它的 oneway 方法会调用 wireFormat.marshal() 去序列化 command
command 就是前面准备发送的 ObjectMessage, 而 wireFormat 就是和它对应的序列化器
那么我们只需要手动 patch 这个方法, 将 command 改成 ExceptionResponse, 将 wireFormat 改成 ExceptionResponseMarshaller 即可
这里我没有分析 OpenWire 协议, 而是用了一个比较取巧的办法
在当前源码目录下新建一个 org.apache.activemq.transport.tcp.TcpTransport
类, 然后重写对应的逻辑, 这样在运行的时候, 因为 classpath 查找顺序的问题, 程序就会优先使用当前源码目录里的 TcpTransport 类
然后是 createThrowable 方法的利用, 这块其实跟 PostgreSQL JDBC 的利用类似, 因为 ActiveMQ 自带 spring 相关依赖, 那么就可以利用 ClassPathXmlApplicationContext 加载 XML 实现 RCE
|
|
因为在 marshal 的时候会调用 o.getClass().getName()
获取类名, 而 getClass 方法无法重写 (final), 所以我在这里同样 patch 了 org.springframework.context.support.ClassPathXmlApplicationContext
, 使其继承 Throwable 类
|
|
最后成功 RCE