Skip to content

Commit f647e5d

Browse files
committed
Use trusted packages in StreamMessage
StreamMessage now uses the same "white list" mechanism as ObjectMessage to avoid some arbitrary code execution on deserialization. Even though StreamMessage is supposed to handle only primitive types, it is still to possible to send a message that contains an arbitrary serializable instance. The consuming application application may then execute code from this class on deserialization. The fix consists in using the list of trusted packages that can be set at the connection factory level. Fixes #135
1 parent 4497022 commit f647e5d

File tree

4 files changed

+146
-6
lines changed

4 files changed

+146
-6
lines changed

src/main/java/com/rabbitmq/jms/client/RMQMessage.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1146,6 +1146,8 @@ static RMQMessage fromMessage(byte[] b, List<String> trustedPackages) throws RMQ
11461146
private static RMQMessage instantiateRmqMessage(String messageClass, List<String> trustedPackages) throws RMQJMSException {
11471147
if(isRmqObjectMessageClass(messageClass)) {
11481148
return instantiateRmqObjectMessageWithTrustedPackages(trustedPackages);
1149+
} else if (isRmqStreamMessageClass(messageClass)) {
1150+
return instantiateRmqStreamMessageWithTrustedPackages(trustedPackages);
11491151
} else {
11501152
try {
11511153
// instantiate the message object with the thread context classloader
@@ -1168,12 +1170,24 @@ private static boolean isRmqObjectMessageClass(String clazz) {
11681170
return RMQObjectMessage.class.getName().equals(clazz);
11691171
}
11701172

1173+
private static boolean isRmqStreamMessageClass(String clazz) {
1174+
return RMQStreamMessage.class.getName().equals(clazz);
1175+
}
1176+
11711177
private static RMQObjectMessage instantiateRmqObjectMessageWithTrustedPackages(List<String> trustedPackages) throws RMQJMSException {
1178+
return (RMQObjectMessage) instantiateRmqMessageWithTrustedPackages(RMQObjectMessage.class.getName(), trustedPackages);
1179+
}
1180+
1181+
private static RMQStreamMessage instantiateRmqStreamMessageWithTrustedPackages(List<String> trustedPackages) throws RMQJMSException {
1182+
return (RMQStreamMessage) instantiateRmqMessageWithTrustedPackages(RMQStreamMessage.class.getName(), trustedPackages);
1183+
}
1184+
1185+
private static RMQMessage instantiateRmqMessageWithTrustedPackages(String messageClazz, List<String> trustedPackages) throws RMQJMSException {
11721186
try {
11731187
// instantiate the message object with the thread context classloader
1174-
Class<?> messageClass = Class.forName(RMQObjectMessage.class.getName(), true, Thread.currentThread().getContextClassLoader());
1188+
Class<?> messageClass = Class.forName(messageClazz, true, Thread.currentThread().getContextClassLoader());
11751189
Constructor<?> constructor = messageClass.getConstructor(List.class);
1176-
return (RMQObjectMessage) constructor.newInstance(trustedPackages);
1190+
return (RMQMessage) constructor.newInstance(trustedPackages);
11771191
} catch (NoSuchMethodException e) {
11781192
throw new RMQJMSException(e);
11791193
} catch (InvocationTargetException e) {

src/main/java/com/rabbitmq/jms/client/message/RMQStreamMessage.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
// Copyright (c) 2013-2020 VMware, Inc. or its affiliates. All rights reserved.
66
package com.rabbitmq.jms.client.message;
77

8+
import com.rabbitmq.jms.util.WhiteListObjectInputStream;
89
import java.io.ByteArrayInputStream;
910
import java.io.ByteArrayOutputStream;
1011
import java.io.EOFException;
@@ -15,6 +16,7 @@
1516
import java.io.ObjectOutputStream;
1617
import java.io.UTFDataFormatException;
1718

19+
import java.util.List;
1820
import javax.jms.JMSException;
1921
import javax.jms.MessageEOFException;
2022
import javax.jms.MessageFormatException;
@@ -47,12 +49,19 @@ public class RMQStreamMessage extends RMQMessage implements StreamMessage {
4749
private volatile transient byte[] buf;
4850
private volatile transient byte[] readbuf = null;
4951

52+
private final List<String> trustedPackages;
53+
54+
public RMQStreamMessage(List<String> trustedPackages) {
55+
this(false, trustedPackages);
56+
}
57+
5058
public RMQStreamMessage() {
51-
this(false);
59+
this(false, WhiteListObjectInputStream.DEFAULT_TRUSTED_PACKAGES);
5260
}
5361

54-
private RMQStreamMessage(boolean reading) {
62+
private RMQStreamMessage(boolean reading, List<String> trustedPackages) {
5563
this.reading = reading;
64+
this.trustedPackages = trustedPackages;
5665
if (!reading) {
5766
this.bout = new ByteArrayOutputStream(RMQMessage.DEFAULT_MESSAGE_BODY_SIZE);
5867
try {
@@ -513,7 +522,7 @@ protected void readBody(ObjectInput inputStream, ByteArrayInputStream bin) throw
513522
inputStream.read(buf);
514523
this.reading = true;
515524
this.bin = new ByteArrayInputStream(buf);
516-
this.in = new ObjectInputStream(this.bin);
525+
this.in = new WhiteListObjectInputStream(this.bin, this.trustedPackages);
517526
}
518527

519528
@Override

src/test/java/com/rabbitmq/integration/tests/ObjectMessageSerializationIT.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626

2727
public class ObjectMessageSerializationIT extends AbstractITQueue {
2828

29-
private static final String QUEUE_NAME = "test.queue." + SimpleQueueMessageDefaultsIT.class.getCanonicalName();
29+
private static final String QUEUE_NAME = "test.queue." + ObjectMessageSerializationIT.class.getCanonicalName();
3030
private static final long TEST_RECEIVE_TIMEOUT = 1000; // one second
3131
private static final java.util.List<String> TRUSTED_PACKAGES = Arrays.asList("java.lang", "com.rabbitmq.jms");
3232

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4+
//
5+
// Copyright (c) 2013-2020 VMware, Inc. or its affiliates. All rights reserved.
6+
package com.rabbitmq.integration.tests;
7+
8+
import static org.junit.jupiter.api.Assertions.assertEquals;
9+
import static org.junit.jupiter.api.Assertions.assertThrows;
10+
import static org.junit.jupiter.api.Assertions.fail;
11+
12+
import com.rabbitmq.jms.admin.RMQConnectionFactory;
13+
import com.rabbitmq.jms.client.message.RMQStreamMessage;
14+
import com.rabbitmq.jms.client.message.TestMessages;
15+
import com.rabbitmq.jms.util.RMQJMSException;
16+
import java.awt.Color;
17+
import java.lang.reflect.Method;
18+
import java.util.Arrays;
19+
import java.util.HashMap;
20+
import java.util.Map;
21+
import javax.jms.Queue;
22+
import javax.jms.QueueReceiver;
23+
import javax.jms.QueueSender;
24+
import javax.jms.QueueSession;
25+
import javax.jms.Session;
26+
import javax.jms.StreamMessage;
27+
import org.junit.jupiter.api.Test;
28+
29+
public class StreamMessageSerializationIT extends AbstractITQueue {
30+
31+
private static final String QUEUE_NAME = "test.queue." + StreamMessageSerializationIT.class.getCanonicalName();
32+
private static final long TEST_RECEIVE_TIMEOUT = 1000; // one second
33+
private static final java.util.List<String> TRUSTED_PACKAGES = Arrays.asList("java.lang", "com.rabbitmq.jms");
34+
35+
@Override
36+
protected void customise(RMQConnectionFactory connectionFactory) {
37+
super.customise(connectionFactory);
38+
connectionFactory.setTrustedPackages(TRUSTED_PACKAGES);
39+
}
40+
41+
protected void testReceiveStreamMessageWithValue(Object value) throws Exception {
42+
try {
43+
queueConn.start();
44+
QueueSession queueSession = queueConn.createQueueSession(false, Session.DUPS_OK_ACKNOWLEDGE);
45+
Queue queue = queueSession.createQueue(QUEUE_NAME);
46+
47+
drainQueue(queueSession, queue);
48+
49+
QueueSender queueSender = queueSession.createSender(queue);
50+
StreamMessage message = (StreamMessage) MessageTestType.STREAM.gen(queueSession, null);
51+
52+
// we simulate an attack from the sender by calling writeObject with a non-primitive value
53+
// (StreamMessage supports only primitive types)
54+
// the value is then sent to the destination and the consumer will have to
55+
// deserialize it and can potentially execute malicious code
56+
Method writeObjectMethod = RMQStreamMessage.class
57+
.getDeclaredMethod("writeObject", Object.class, boolean.class);
58+
writeObjectMethod.setAccessible(true);
59+
writeObjectMethod.invoke(message, value, true);
60+
61+
queueSender.send(message);
62+
} finally {
63+
reconnect(Arrays.asList("java.lang", "com.rabbitmq.jms"));
64+
}
65+
66+
queueConn.start();
67+
QueueSession queueSession = queueConn.createQueueSession(false, Session.DUPS_OK_ACKNOWLEDGE);
68+
Queue queue = queueSession.createQueue(QUEUE_NAME);
69+
QueueReceiver queueReceiver = queueSession.createReceiver(queue);
70+
RMQStreamMessage m = (RMQStreamMessage) queueReceiver.receive(TEST_RECEIVE_TIMEOUT);
71+
MessageTestType.STREAM.check(m, null);
72+
assertEquals(m.readObject(), value);
73+
}
74+
75+
@Test
76+
public void testReceiveStreamMessageWithPrimitiveValue() throws Exception {
77+
testReceiveStreamMessageWithValue(1024L);
78+
testReceiveStreamMessageWithValue("a string");
79+
}
80+
81+
@Test
82+
public void testReceiveStreamMessageWithTrustedValue() throws Exception {
83+
testReceiveStreamMessageWithValue(new TestMessages.TestSerializable(8, "An object"));
84+
}
85+
86+
@Test
87+
public void testReceiveStreamMessageWithUntrustedValue1() throws Exception {
88+
// StreamMessage cannot be used with a Map, unless the sender uses a trick
89+
// this is to simulate an attack from the sender
90+
// Note: java.util is not on the trusted package list
91+
assertThrows(RMQJMSException.class, () -> {
92+
Map<String, String> m = new HashMap<String, String>();
93+
m.put("key", "value");
94+
testReceiveStreamMessageWithValue(m);
95+
});
96+
}
97+
@Test
98+
public void testReceiveStreamMessageWithUntrustedValue2() throws Exception {
99+
// StreamMessage cannot be used with a Map, unless the sender uses a trick
100+
// this is to simulate an attack from the sender
101+
// java.awt is not on the trusted package list
102+
assertThrows(RMQJMSException.class, () -> {
103+
testReceiveStreamMessageWithValue(Color.WHITE);
104+
});
105+
}
106+
107+
protected void reconnect(java.util.List<String> trustedPackages) throws Exception {
108+
if (queueConn != null) {
109+
this.queueConn.close();
110+
((RMQConnectionFactory) connFactory).setTrustedPackages(trustedPackages);
111+
this.queueConn = connFactory.createQueueConnection();
112+
} else {
113+
fail("Cannot reconnect");
114+
}
115+
}
116+
}
117+

0 commit comments

Comments
 (0)