From 322eef2942df6d7555090432dd8d651fac7491a3 Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Mon, 11 May 2026 15:02:55 +0100 Subject: [PATCH 01/11] RfcParser rejects invalid IPv6 host Fix gh-36787 --- .../java/org/springframework/web/util/RfcUriParser.java | 8 ++++++-- .../web/util/UriComponentsBuilderTests.java | 5 ++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/util/RfcUriParser.java b/spring-web/src/main/java/org/springframework/web/util/RfcUriParser.java index c87c2a7b123..3198e93590a 100644 --- a/spring-web/src/main/java/org/springframework/web/util/RfcUriParser.java +++ b/spring-web/src/main/java/org/springframework/web/util/RfcUriParser.java @@ -241,12 +241,16 @@ abstract class RfcUriParser { parser.index(++i); parser.captureHost(); if (parser.hasNext()) { - if (parser.charAtIndex() == ':') { + char next = parser.charAtIndex(); + if (next == ':') { parser.advanceTo(PORT, i + 1); } - else { + else if (next == '/') { parser.advanceTo(PATH, i); } + else { + fail(parser, "Bad authority"); + } } break; case ':': diff --git a/spring-web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java b/spring-web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java index e8729d97d9b..fc0a40a54c3 100644 --- a/spring-web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java +++ b/spring-web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java @@ -267,7 +267,10 @@ class UriComponentsBuilderTests { @EnumSource void fromUriStringInvalidIPv6Host(ParserType parserType) { assertThatIllegalArgumentException().isThrownBy(() -> - UriComponentsBuilder.fromUriString("http://[1abc:2abc:3abc::5ABC:6abc:8080/resource", parserType)); + UriComponentsBuilder.fromUriString("https://[1abc:2abc:3abc::5ABC:6abc:8080/resource", parserType)); + + assertThatIllegalArgumentException().isThrownBy(() -> + UriComponentsBuilder.fromUriString("https://[1abc:2abc:3abc::5ABC:6abc]resource", parserType)); } @ParameterizedTest // gh-36759 From fe68e337b2387697ab2f3f4609e2e0b72b2d580b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Deleuze?= Date: Fri, 1 May 2026 17:26:16 +0200 Subject: [PATCH 02/11] Add trusted packages to JacksonJsonMessageConverter This commit introduces trusted packages, specified via the related setter for untrusted use cases. It allows explicit configuration of which Java packages are allowed to be deserialized. See gh-36791 --- .../JacksonJsonMessageConverter.java | 40 +++++++++ .../JacksonJsonMessageConverterTests.java | 81 +++++++++++++++++++ 2 files changed, 121 insertions(+) diff --git a/spring-jms/src/main/java/org/springframework/jms/support/converter/JacksonJsonMessageConverter.java b/spring-jms/src/main/java/org/springframework/jms/support/converter/JacksonJsonMessageConverter.java index a8a70006672..bb9f5f1977a 100644 --- a/spring-jms/src/main/java/org/springframework/jms/support/converter/JacksonJsonMessageConverter.java +++ b/spring-jms/src/main/java/org/springframework/jms/support/converter/JacksonJsonMessageConverter.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; @@ -48,6 +49,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...)}. + * * @author Sebastien Deleuze * @since 7.0 */ @@ -73,6 +76,8 @@ public class JacksonJsonMessageConverter implements SmartMessageConverter, BeanC private final Map, String> classIdMappings = new HashMap<>(); + private String @Nullable [] trustedPackages; + private @Nullable ClassLoader beanClassLoader; @@ -80,6 +85,7 @@ public class JacksonJsonMessageConverter implements SmartMessageConverter, BeanC * Construct a new instance with a {@link JsonMapper} customized with the * {@link tools.jackson.databind.JacksonModule}s found by * {@link MapperBuilder#findModules(ClassLoader)}. + * @see #setTrustedPackages(String...) */ public JacksonJsonMessageConverter() { this(JsonMapper.builder()); @@ -89,6 +95,8 @@ public class JacksonJsonMessageConverter implements SmartMessageConverter, BeanC * Construct a new instance with the provided {@link JsonMapper.Builder} * customized with the {@link tools.jackson.databind.JacksonModule}s found * by {@link MapperBuilder#findModules(ClassLoader)}. + * @param builder the mapper builder to use + * @see #setTrustedPackages(String...) * @see JsonMapper#builder() */ public JacksonJsonMessageConverter(JsonMapper.Builder builder) { @@ -98,6 +106,8 @@ public class JacksonJsonMessageConverter implements SmartMessageConverter, BeanC /** * Construct a new instance with the provided {@link JsonMapper}. + * @param mapper the mapper to use + * @see #setTrustedPackages(String...) * @see JsonMapper#builder() */ public JacksonJsonMessageConverter(JsonMapper mapper) { @@ -105,6 +115,15 @@ public class JacksonJsonMessageConverter implements SmartMessageConverter, BeanC this.mapper = mapper; } + /** + * Specify the trusted Java packages for deserialization. + * @param trustedPackages the trusted Java packages for deserialization + * @since 7.0.8 + */ + public void setTrustedPackages(String... trustedPackages) { + this.trustedPackages = trustedPackages.clone(); + } + /** * Specify whether {@link #toMessage(Object, Session)} should marshal to a * {@link BytesMessage} or a {@link TextMessage}. @@ -168,6 +187,23 @@ public class JacksonJsonMessageConverter implements SmartMessageConverter, BeanC }); } + 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; @@ -445,6 +481,10 @@ public class JacksonJsonMessageConverter implements SmartMessageConverter, BeanC if (mappedClass != null) { return this.mapper.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.mapper.constructType(typeClass); diff --git a/spring-jms/src/test/java/org/springframework/jms/support/converter/JacksonJsonMessageConverterTests.java b/spring-jms/src/test/java/org/springframework/jms/support/converter/JacksonJsonMessageConverterTests.java index b01f421512e..d67b68003bd 100644 --- a/spring-jms/src/test/java/org/springframework/jms/support/converter/JacksonJsonMessageConverterTests.java +++ b/spring-jms/src/test/java/org/springframework/jms/support/converter/JacksonJsonMessageConverterTests.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; @@ -131,6 +132,86 @@ class JacksonJsonMessageConverterTests { assertThat(unmarshalled).as("Invalid result").isEqualTo(result); } + @Test + void fromTextMessageUntrusted() throws Exception { + converter = new JacksonJsonMessageConverter(); + 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 JacksonJsonMessageConverter(); + 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 JacksonJsonMessageConverter(); + 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 JacksonJsonMessageConverter(); + 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 JacksonJsonMessageConverter(); + 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(); From 9bec52b1ec741976658e916850b31f41afd2da2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Deleuze?= Date: Fri, 1 May 2026 18:56:16 +0200 Subject: [PATCH 03/11] Add trusted packages to MappingJackson2MessageConverter This commit introduces trusted packages, specified via the related setter for untrusted use cases. It allows explicit configuration of which Java packages are allowed to be deserialized. Closes gh-36791 --- .../MappingJackson2MessageConverter.java | 37 +++++++++ .../MappingJackson2MessageConverterTests.java | 82 +++++++++++++++++++ 2 files changed, 119 insertions(+) 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: *