Skip to content

Commit cef06dd

Browse files
Limit ObjectMessage deserialization with a package white list
ObjectMessages in JMS can be used to transfer arbitrary Java objects which are then deserialized before or at the time of being passed on to message listeners. This opens message consumers up to a range of attacks that exploit issues in Java object serialization. Note: attacker must authenticate with RabbitMQ in order to carry out the attack. With this commit we introduce a way to white list packages that can be deserialized when ObjectMessages are consumed: * Using a system property, com.rabbitmq.jms.TrustedPackagesPrefixes * Using RMQSession#setTrustedPackages(List<String>) By default all packages are trusted for backwards compatibility (as it's not possible for library maintainers to cover application-specific packages). A CVE has been allocated for this issue and will be announced once a new release is published. Related issues in other messaging libraries and services: * Spring AMQP ticket: https://jira.spring.io/browse/AMQP-590 * ActiveMQ ticket: https://issues.apache.org/jira/browse/AMQ-6013 Note: this includes a number of small drive-by changes: * TLS tests are now executed conditionally and excluded by default * Many minor IDEA code analysis fixes * JavaDoc warning fixes
1 parent 3b907c4 commit cef06dd

20 files changed

+663
-96
lines changed

src/main/java/com/rabbitmq/jms/admin/RMQConnectionFactory.java

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import java.io.IOException;
99
import java.io.Serializable;
10+
import java.util.List;
1011
import java.util.concurrent.TimeoutException;
1112

1213
import javax.jms.Connection;
@@ -23,6 +24,7 @@
2324
import javax.naming.StringRefAddr;
2425
import javax.net.ssl.SSLException;
2526

27+
import com.rabbitmq.jms.util.WhiteListObjectInputStream;
2628
import org.slf4j.Logger;
2729
import org.slf4j.LoggerFactory;
2830

@@ -64,6 +66,13 @@ public class RMQConnectionFactory implements ConnectionFactory, Referenceable, S
6466
/** The time to wait for threads/messages to terminate during {@link Connection#close()} */
6567
private volatile long terminationTimeout = Long.getLong("rabbit.jms.terminationTimeout", 15000);
6668

69+
/**
70+
* Classes in these packages can be transferred via ObjectMessage.
71+
*
72+
* @see WhiteListObjectInputStream
73+
*/
74+
private List<String> trustedPackages = WhiteListObjectInputStream.DEFAULT_TRUSTED_PACKAGES;
75+
6776
/**
6877
* {@inheritDoc}
6978
*/
@@ -144,7 +153,7 @@ public String toString() {
144153
/**
145154
* Set connection factory parameters by URI String.
146155
* @param uriString URI to use for instantiated connection
147-
* @throws JMSException
156+
* @throws JMSException if connection URI is invalid
148157
*/
149158
public void setUri(String uriString) throws JMSException {
150159
logger.trace("Set connection factory parameters by URI '{}'", uriString);
@@ -160,7 +169,14 @@ public void setUri(String uriString) throws JMSException {
160169
this.virtualHost = factory.getVirtualHost();
161170
}
162171

163-
private static final void setRabbitUri(Logger logger, RMQConnectionFactory rmqFactory, com.rabbitmq.client.ConnectionFactory factory, String uriString) throws RMQJMSException {
172+
/**
173+
* @param value list of trusted package prefixes
174+
*/
175+
public void setTrustedPackages(List<String> value) {
176+
this.trustedPackages = value;
177+
}
178+
179+
private static void setRabbitUri(Logger logger, RMQConnectionFactory rmqFactory, com.rabbitmq.client.ConnectionFactory factory, String uriString) throws RMQJMSException {
164180
if (uriString != null) { // we get the defaults if the uri is null
165181
try {
166182
factory.setUri(uriString);
@@ -189,21 +205,21 @@ public void setSsl(boolean ssl) {
189205
this.ssl = ssl;
190206
}
191207

192-
private static final String scheme(boolean isSsl) {
208+
private static String scheme(boolean isSsl) {
193209
return (isSsl ? "amqps" : "amqp");
194210
}
195211

196-
private static final String uriUInfoEscape(String user, String pass) {
197-
if (null==user) return null;
198-
if (null==pass) return encUserinfo(user, "UTF-8");
212+
private static String uriUInfoEscape(String user, String pass) {
213+
if (null == user) return null;
214+
if (null == pass) return encUserinfo(user, "UTF-8");
199215
return encUserinfo(user + ":" + pass, "UTF-8");
200216
}
201217

202-
private static final String uriHostEscape(String host) {
218+
private static String uriHostEscape(String host) {
203219
return encHost(host, "UTF-8");
204220
}
205221

206-
private static final String uriVirtualHostEscape(String vHost) {
222+
private static String uriVirtualHostEscape(String vHost) {
207223
return encSegment(vHost, "UTF-8");
208224
}
209225

@@ -224,9 +240,9 @@ public Reference getReference() throws NamingException {
224240
* @param propertyName - the name of the property
225241
* @param value - the value to store with the property
226242
*/
227-
private static final void addStringRefProperty(Reference ref,
228-
String propertyName,
229-
String value) {
243+
private static void addStringRefProperty(Reference ref,
244+
String propertyName,
245+
String value) {
230246
if (value==null || propertyName==null) return;
231247
RefAddr ra = new StringRefAddr(propertyName, value);
232248
ref.add(ra);
@@ -238,10 +254,10 @@ private static final void addStringRefProperty(Reference ref,
238254
* @param propertyName - the name of the property
239255
* @param value - the value to store with the property
240256
*/
241-
private static final void addIntegerRefProperty(Reference ref,
242-
String propertyName,
243-
Integer value) {
244-
if (value==null || propertyName==null) return;
257+
private static void addIntegerRefProperty(Reference ref,
258+
String propertyName,
259+
Integer value) {
260+
if (value == null || propertyName == null) return;
245261
RefAddr ra = new StringRefAddr(propertyName, String.valueOf(value));
246262
ref.add(ra);
247263
}

src/main/java/com/rabbitmq/jms/admin/RMQDestination.java

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -142,12 +142,16 @@ public boolean amqpReadable() {
142142
}
143143

144144
/**
145-
* @return <code>true</code> if this is an AMQP mapped resource, <code>false</code> otherwise
145+
* @return <code>true</code> if this is an AMQP 0-9-1 mapped resource, <code>false</code> otherwise
146146
*/
147147
public boolean isAmqp() {
148148
return this.amqp;
149149
}
150-
/** For JNDI binding and Spring beans */
150+
151+
/**
152+
* For JNDI binding and Spring beans
153+
* @param amqp set to <code>true</code> if this is an AMQP 0-9-1 mapped resource, <code>false</code> otherwise
154+
*/
151155
public void setAmqp(boolean amqp) {
152156
if (this.isDeclared())
153157
throw new IllegalStateException();
@@ -158,7 +162,10 @@ public void setAmqp(boolean amqp) {
158162
public String getAmqpQueueName() {
159163
return this.amqpQueueName;
160164
}
161-
/** For JNDI binding and Spring beans */
165+
/**
166+
* For JNDI binding and Spring beans
167+
* @param amqpQueueName AMQP 0-9-1 queue name
168+
*/
162169
public void setAmqpQueueName(String amqpQueueName) {
163170
if (this.isDeclared())
164171
throw new IllegalStateException();
@@ -167,7 +174,10 @@ public void setAmqpQueueName(String amqpQueueName) {
167174
public String getAmqpExchangeName() {
168175
return this.amqpExchangeName;
169176
}
170-
/** For JNDI binding and Spring beans */
177+
/**
178+
* For JNDI binding and Spring beans
179+
* @param amqpExchangeName AMQP 0-9-1 exchange name to use
180+
*/
171181
public void setAmqpExchangeName(String amqpExchangeName) {
172182
if (this.isDeclared())
173183
throw new IllegalStateException();
@@ -176,23 +186,36 @@ public void setAmqpExchangeName(String amqpExchangeName) {
176186
public String getDestinationName() {
177187
return this.destinationName;
178188
}
179-
/** For JNDI binding and Spring beans */
189+
/**
190+
* For JNDI binding and Spring beans
191+
* @param destinationName JMS destination name
192+
*/
180193
public void setDestinationName(String destinationName) {
181194
if (isDeclared())
182195
throw new IllegalStateException();
183196
this.destinationName = destinationName;
184197
}
198+
199+
/**
200+
* @return AMQP 0-9-1 routing key
201+
*/
185202
public String getAmqpRoutingKey() {
186203
return this.amqpRoutingKey;
187204
}
188-
/** For JNDI binding and Spring beans */
205+
/**
206+
* For JNDI binding and Spring beans
207+
* @param routingKey AMQP 0-9-1 routing key
208+
*/
189209
public void setAmqpRoutingKey(String routingKey) {
190210
if (isDeclared())
191211
throw new IllegalStateException();
192212
this.amqpRoutingKey = routingKey;
193213
}
194214

195-
/** Internal use only */
215+
/**
216+
* Internal use only
217+
* @return AMQP 0-9-1 exchange type used
218+
*/
196219
public String amqpExchangeType() {
197220
return queueOrTopicExchangeType(this.isQueue);
198221
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public void setComplete() {
2222

2323
/**
2424
* Non-blocking snapshot test for completion.
25+
* @return <code>true</code> if this operation has completed, <code>false</code> otherwise
2526
*/
2627
public boolean isComplete() {
2728
return this.fb.isComplete();

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@ public DeliveryExecutor(long onMessageTimeoutMs) {
5353
* only one <code>onMessage</code> call being executed at any one time.
5454
*
5555
* @param rmqMessage the message to deliver
56+
* @param messageListener JMS message listener that will handle the delivery
5657
* @throws JMSException if the delivery takes too long and is aborted
58+
* @throws InterruptedException if executing thread is interrupted
5759
*/
5860
public void deliverMessageWithProtection(RMQMessage rmqMessage, MessageListener messageListener) throws JMSException, InterruptedException {
5961
try {

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import javax.jms.TopicConnection;
2828
import javax.jms.TopicSession;
2929

30+
import com.rabbitmq.jms.util.WhiteListObjectInputStream;
3031
import org.slf4j.Logger;
3132
import org.slf4j.LoggerFactory;
3233

@@ -77,6 +78,13 @@ public class RMQConnection implements Connection, QueueConnection, TopicConnecti
7778
/** This is used for JMSCTS test cases, as ClientID should only be configurable right after the connection has been created */
7879
private volatile boolean canSetClientID = true;
7980

81+
/**
82+
* Classes in these packages can be transferred via ObjectMessage.
83+
*
84+
* @see WhiteListObjectInputStream
85+
*/
86+
private List<String> trustedPackages = WhiteListObjectInputStream.DEFAULT_TRUSTED_PACKAGES;
87+
8088
/**
8189
* Creates an RMQConnection object.
8290
* @param rabbitConnection the TCP connection wrapper to the RabbitMQ broker
@@ -113,6 +121,7 @@ public Session createSession(boolean transacted, int acknowledgeMode) throws JMS
113121
illegalStateExceptionIfClosed();
114122
freezeClientID();
115123
RMQSession session = new RMQSession(this, transacted, acknowledgeMode, this.subscriptions);
124+
session.setTrustedPackages(this.trustedPackages);
116125
this.sessions.add(session);
117126
return session;
118127
}
@@ -154,6 +163,19 @@ public void setClientID(String clientID) throws JMSException {
154163

155164
}
156165

166+
public List<String> getTrustedPackages() {
167+
return trustedPackages;
168+
}
169+
170+
/**
171+
* @param value list of trusted package prefixes
172+
*
173+
* @see com.rabbitmq.jms.admin.RMQConnectionFactory#setTrustedPackages(List)
174+
*/
175+
public void setTrustedPackages(List<String> value) {
176+
this.trustedPackages = value;
177+
}
178+
157179
/**
158180
* {@inheritDoc}
159181
*/

0 commit comments

Comments
 (0)