diff --git a/spring-jms/src/main/java/org/springframework/jms/support/converter/MappingJackson2MessageConverter.java b/spring-jms/src/main/java/org/springframework/jms/support/converter/MappingJackson2MessageConverter.java
index c74ef2a81ab..e842f023496 100644
--- a/spring-jms/src/main/java/org/springframework/jms/support/converter/MappingJackson2MessageConverter.java
+++ b/spring-jms/src/main/java/org/springframework/jms/support/converter/MappingJackson2MessageConverter.java
@@ -21,6 +21,7 @@ import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@@ -49,6 +50,8 @@ import org.springframework.util.ClassUtils;
* {@link #setTargetType targetType} is set to {@link MessageType#TEXT}.
* Converts from a {@link TextMessage} or {@link BytesMessage} to an object.
*
+ *
For untrusted environments, use {@link #setTrustedPackages(String...)}.
+ *
*
It customizes Jackson's default properties with the following ones:
*
* - {@link MapperFeature#DEFAULT_VIEW_INCLUSION} is disabled
@@ -59,6 +62,7 @@ import org.springframework.util.ClassUtils;
* @author Dave Syer
* @author Juergen Hoeller
* @author Stephane Nicoll
+ * @author Sebastien Deleuze
* @since 3.1.4
* @deprecated since 7.0 in favor of {@link JacksonJsonMessageConverter}
*/
@@ -85,11 +89,14 @@ public class MappingJackson2MessageConverter implements SmartMessageConverter, B
private final Map, String> classIdMappings = new HashMap<>();
+ private String @Nullable [] trustedPackages;
+
private @Nullable ClassLoader beanClassLoader;
/**
* Construct a {@code MappingJackson2MessageConverter} with a default {@link ObjectMapper}.
+ * @see #setTrustedPackages(String...)
*/
@SuppressWarnings("deprecation") // on Jackson 2.13: configure(MapperFeature, boolean)
public MappingJackson2MessageConverter() {
@@ -102,12 +109,21 @@ public class MappingJackson2MessageConverter implements SmartMessageConverter, B
* Construct a {@code MappingJackson2MessageConverter} with a custom {@link ObjectMapper}.
* @param objectMapper the {@code ObjectMapper} to use
* @since 6.1
+ * @see #setTrustedPackages(String...)
*/
public MappingJackson2MessageConverter(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "ObjectMapper must not be null");
this.objectMapper = objectMapper;
}
+ /**
+ * Specify the trusted Java packages for deserialization.
+ * @param trustedPackages the trusted Java packages for deserialization
+ * @since 6.2.19
+ */
+ public void setTrustedPackages(String... trustedPackages) {
+ this.trustedPackages = trustedPackages.clone();
+ }
/**
* Set the {@code ObjectMapper} for this converter.
@@ -181,6 +197,23 @@ public class MappingJackson2MessageConverter implements SmartMessageConverter, B
});
}
+ private boolean isTrustedPackage(String requestedType) {
+ if (this.trustedPackages != null) {
+ String packageName = ClassUtils.getPackageName(requestedType);
+ int lastBracketIndex = packageName.lastIndexOf('[');
+ if (lastBracketIndex != -1 && packageName.length() > lastBracketIndex + 1 && packageName.charAt(lastBracketIndex + 1) == 'L') {
+ packageName = packageName.substring(lastBracketIndex + 2);
+ }
+ for (String trustedPackage : this.trustedPackages) {
+ if (packageName.equals(trustedPackage)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ return true;
+ }
+
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.beanClassLoader = classLoader;
@@ -461,6 +494,10 @@ public class MappingJackson2MessageConverter implements SmartMessageConverter, B
if (mappedClass != null) {
return this.objectMapper.constructType(mappedClass);
}
+ if (!isTrustedPackage(typeId)) {
+ throw new MessageConversionException("The class '" + typeId + "' is not in the trusted packages: " +
+ Arrays.toString(this.trustedPackages));
+ }
try {
Class> typeClass = ClassUtils.forName(typeId, this.beanClassLoader);
return this.objectMapper.constructType(typeClass);
diff --git a/spring-jms/src/test/java/org/springframework/jms/support/converter/MappingJackson2MessageConverterTests.java b/spring-jms/src/test/java/org/springframework/jms/support/converter/MappingJackson2MessageConverterTests.java
index f93f292f1aa..52bd6274538 100644
--- a/spring-jms/src/test/java/org/springframework/jms/support/converter/MappingJackson2MessageConverterTests.java
+++ b/spring-jms/src/test/java/org/springframework/jms/support/converter/MappingJackson2MessageConverterTests.java
@@ -37,6 +37,7 @@ import org.mockito.stubbing.Answer;
import org.springframework.core.MethodParameter;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.isA;
@@ -48,6 +49,7 @@ import static org.mockito.Mockito.verify;
* @author Arjen Poutsma
* @author Dave Syer
* @author Stephane Nicoll
+ * @author Sebastien Deleuze
*/
@SuppressWarnings("removal")
class MappingJackson2MessageConverterTests {
@@ -134,6 +136,86 @@ class MappingJackson2MessageConverterTests {
assertThat(unmarshalled).as("Invalid result").isEqualTo(result);
}
+ @Test
+ void fromTextMessageUntrusted() throws Exception {
+ converter = new MappingJackson2MessageConverter();
+ converter.setTrustedPackages("java.lang");
+ converter.setTypeIdPropertyName("__typeid__");
+ TextMessage textMessageMock = mock();
+
+ String text = "{\"foo\":\"bar\"}";
+ given(textMessageMock.getStringProperty("__typeid__")).willReturn(MyBean.class.getName());
+ given(textMessageMock.getText()).willReturn(text);
+
+ assertThatExceptionOfType(MessageConversionException.class)
+ .isThrownBy(() -> converter.fromMessage(textMessageMock))
+ .withMessageContaining("is not in the trusted packages");
+ }
+
+ @Test
+ void fromTextMessageTrusted() throws Exception {
+ converter = new MappingJackson2MessageConverter();
+ converter.setTrustedPackages("java.lang", "org.springframework.jms.support.converter");
+ converter.setTypeIdPropertyName("__typeid__");
+ TextMessage textMessageMock = mock();
+ MyBean unmarshalled = new MyBean("bar");
+
+ String text = "{\"foo\":\"bar\"}";
+ given(textMessageMock.getStringProperty("__typeid__")).willReturn(MyBean.class.getName());
+ given(textMessageMock.getText()).willReturn(text);
+
+ MyBean result = (MyBean) converter.fromMessage(textMessageMock);
+ assertThat(unmarshalled).as("Invalid result").isEqualTo(result);
+ }
+
+ @Test
+ void fromTextMessageTrustedEmpty() throws Exception {
+ converter = new MappingJackson2MessageConverter();
+ converter.setTrustedPackages();
+ converter.setTypeIdPropertyName("__typeid__");
+ TextMessage textMessageMock = mock();
+
+ String text = "{\"foo\":\"bar\"}";
+ given(textMessageMock.getStringProperty("__typeid__")).willReturn(MyBean.class.getName());
+ given(textMessageMock.getText()).willReturn(text);
+
+ assertThatExceptionOfType(MessageConversionException.class)
+ .isThrownBy(() -> converter.fromMessage(textMessageMock))
+ .withMessageContaining("is not in the trusted packages");
+ }
+
+ @Test
+ void fromTextMessageTrusted1DArray() throws Exception {
+ converter = new MappingJackson2MessageConverter();
+ converter.setTrustedPackages("org.springframework.jms.support.converter");
+ converter.setTypeIdPropertyName("__typeid__");
+ TextMessage textMessageMock = mock();
+ MyBean[] unmarshalled = new MyBean[] { new MyBean("bar") };
+
+ String text = "[{\"foo\":\"bar\"}]";
+ given(textMessageMock.getStringProperty("__typeid__")).willReturn("[L" + MyBean.class.getName() + ";");
+ given(textMessageMock.getText()).willReturn(text);
+
+ MyBean[] result = (MyBean[]) converter.fromMessage(textMessageMock);
+ assertThat(unmarshalled).as("Invalid result").isEqualTo(result);
+ }
+
+ @Test
+ void fromTextMessageTrusted2DArray() throws Exception {
+ converter = new MappingJackson2MessageConverter();
+ converter.setTrustedPackages("org.springframework.jms.support.converter");
+ converter.setTypeIdPropertyName("__typeid__");
+ TextMessage textMessageMock = mock();
+ MyBean[][] unmarshalled = new MyBean[][] { { new MyBean("bar") } };
+
+ String text = "[[{\"foo\":\"bar\"}]]";
+ given(textMessageMock.getStringProperty("__typeid__")).willReturn("[[L" + MyBean.class.getName() + ";");
+ given(textMessageMock.getText()).willReturn(text);
+
+ MyBean[][] result = (MyBean[][]) converter.fromMessage(textMessageMock);
+ assertThat(unmarshalled).as("Invalid result").isEqualTo(result);
+ }
+
@Test
void fromTextMessageWithUnknownProperty() throws Exception {
TextMessage textMessageMock = mock();