Skip to content

Commit b69839e

Browse files
Merge pull request #136 from rabbitmq/rabbitmq-jms-client-135-white-list-stream-message
Use trusted packages in StreamMessage
2 parents 4497022 + f647e5d commit b69839e

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)