From 0d495294ed19ca0ab66d98d22bb867b4954bed4f Mon Sep 17 00:00:00 2001 From: Gary Russell Date: Fri, 8 Sep 2017 13:58:59 -0400 Subject: [PATCH] INT-4342: White List for Payload Deserializer JIRA: https://jira.spring.io/browse/INT-4342 Use similar code to Spring AMQP to add white list support for Integration's use of the `DeserializingMessageConverter`; introduce the `WhiteListDeserializingMessageConverter`. Polishing Missed this change in PR. Fix XSD attribute --- ...PayloadDeserializingTransformerParser.java | 4 +- .../integration/dsl/Transformers.java | 8 +- .../WhiteListDeserializingConverter.java | 182 ++++++++++++++++++ .../PayloadDeserializingTransformer.java | 44 ++++- .../config/spring-integration-5.0.xsd | 11 +- ...ializingTransformerParserTests-context.xml | 9 +- ...adDeserializingTransformerParserTests.java | 14 ++ .../dsl/flows/IntegrationFlowTests.java | 18 +- .../PayloadDeserializingTransformerTests.java | 29 +++ .../jdbc/store/JdbcChannelMessageStore.java | 19 +- .../jdbc/store/JdbcMessageStore.java | 19 +- .../jdbc/store/channel/MessageRowMapper.java | 8 +- .../mongodb/store/MongoDbMessageStore.java | 23 ++- .../support/BinaryToMessageConverter.java | 15 +- .../support/MongoDbMessageBytesConverter.java | 15 +- src/reference/asciidoc/transformer.adoc | 6 +- 16 files changed, 385 insertions(+), 39 deletions(-) create mode 100644 spring-integration-core/src/main/java/org/springframework/integration/support/converter/WhiteListDeserializingConverter.java diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/PayloadDeserializingTransformerParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/PayloadDeserializingTransformerParser.java index 215b613aff..c28a609f75 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/PayloadDeserializingTransformerParser.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/PayloadDeserializingTransformerParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ import org.springframework.integration.transformer.PayloadDeserializingTransform * Parser for the 'payload-deserializing-transformer' element. * * @author Mark Fisher + * @author Gary Russell */ public class PayloadDeserializingTransformerParser extends AbstractTransformerParser { @@ -37,6 +38,7 @@ public class PayloadDeserializingTransformerParser extends AbstractTransformerPa @Override protected void parseTransformer(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "deserializer"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "white-list", "whiteListPatterns"); } } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/Transformers.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/Transformers.java index c136433eea..776d6f9f47 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/dsl/Transformers.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/Transformers.java @@ -165,12 +165,14 @@ public abstract class Transformers { return transformer; } - public static PayloadDeserializingTransformer deserializer() { - return deserializer(null); + public static PayloadDeserializingTransformer deserializer(String... whiteListPatterns) { + return deserializer(null, whiteListPatterns); } - public static PayloadDeserializingTransformer deserializer(Deserializer deserializer) { + public static PayloadDeserializingTransformer deserializer(Deserializer deserializer, + String... whiteListPatterns) { PayloadDeserializingTransformer transformer = new PayloadDeserializingTransformer(); + transformer.setWhiteListPatterns(whiteListPatterns); if (deserializer != null) { transformer.setDeserializer(deserializer); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/support/converter/WhiteListDeserializingConverter.java b/spring-integration-core/src/main/java/org/springframework/integration/support/converter/WhiteListDeserializingConverter.java new file mode 100644 index 0000000000..b073121f51 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/support/converter/WhiteListDeserializingConverter.java @@ -0,0 +1,182 @@ +/* + * Copyright 2002-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.support.converter; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectStreamClass; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.springframework.beans.DirectFieldAccessor; +import org.springframework.core.ConfigurableObjectInputStream; +import org.springframework.core.NestedIOException; +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.serializer.DefaultDeserializer; +import org.springframework.core.serializer.Deserializer; +import org.springframework.core.serializer.support.SerializationFailedException; +import org.springframework.util.Assert; +import org.springframework.util.PatternMatchUtils; + +/** + * A {@link Converter} that delegates to a + * {@link org.springframework.core.serializer.Deserializer} to convert data in a byte + * array to an object. By default, if using a {@link DefaultDeserializer} all + * classes/packages are deserialized. If you receive data from untrusted sources, consider + * adding trusted classes/packages using {@link #setWhiteListPatterns(String...)} or + * {@link #addWhiteListPatterns(String...)}. + * + * @author Gary Russell + * @author Mark Fisher + * @author Juergen Hoeller + * @since 4.2.13 + */ +public class WhiteListDeserializingConverter implements Converter { + + private final Deserializer deserializer; + + private final ClassLoader defaultDeserializerClassLoader; + + private final boolean usingDefaultDeserializer; + + private final Set whiteListPatterns = new LinkedHashSet(); + + + /** + * Create a {@code WhiteListDeserializingConverter} with default + * {@link java.io.ObjectInputStream} configuration, using the "latest user-defined + * ClassLoader". + */ + public WhiteListDeserializingConverter() { + this(new DefaultDeserializer()); + } + + /** + * Create a {@code WhiteListDeserializingConverter} for using an + * {@link java.io.ObjectInputStream} with the given {@code ClassLoader}. + * @param classLoader the class loader to use for deserialization. + */ + public WhiteListDeserializingConverter(ClassLoader classLoader) { + this(new DefaultDeserializer(classLoader)); + } + + /** + * Create a {@code WhiteListDeserializingConverter} that delegates to the provided + * {@link Deserializer}. + * @param deserializer the deserializer to use. + */ + public WhiteListDeserializingConverter(Deserializer deserializer) { + Assert.notNull(deserializer, "Deserializer must not be null"); + this.deserializer = deserializer; + if (deserializer instanceof DefaultDeserializer) { + ClassLoader classLoader = null; + try { + classLoader = (ClassLoader) new DirectFieldAccessor(deserializer).getPropertyValue("classLoader"); + } + catch (Exception e) { + // no-op + } + this.defaultDeserializerClassLoader = classLoader; + this.usingDefaultDeserializer = true; + } + else { + this.defaultDeserializerClassLoader = null; + this.usingDefaultDeserializer = false; + } + } + + /** + * Set simple patterns for allowable packages/classes for deserialization. + * The patterns will be applied in order until a match is found. + * A class can be fully qualified or a wildcard '*' is allowed at the + * beginning or end of the class name. + * Examples: {@code com.foo.*}, {@code *.MyClass}. + * @param whiteListPatterns the patterns. + */ + public void setWhiteListPatterns(String... whiteListPatterns) { + this.whiteListPatterns.clear(); + Collections.addAll(this.whiteListPatterns, whiteListPatterns); + } + + /** + * Add package/class patterns to the white list. + * @param patterns the patterns to add. + * @see #setWhiteListPatterns(String...) + */ + public void addWhiteListPatterns(String... patterns) { + Collections.addAll(this.whiteListPatterns, patterns); + } + + @Override + public Object convert(byte[] source) { + ByteArrayInputStream byteStream = new ByteArrayInputStream(source); + try { + if (this.usingDefaultDeserializer) { + return deserialize(byteStream); + } + else { + return this.deserializer.deserialize(byteStream); + } + } + catch (Throwable ex) { + throw new SerializationFailedException("Failed to deserialize payload. " + + "Is the byte array a result of corresponding serialization for " + + this.deserializer.getClass().getSimpleName() + "?", ex); + } + } + + protected Object deserialize(ByteArrayInputStream inputStream) throws IOException { + try { + ObjectInputStream objectInputStream = new ConfigurableObjectInputStream(inputStream, + this.defaultDeserializerClassLoader) { + + @Override + protected Class resolveClass(ObjectStreamClass classDesc) + throws IOException, ClassNotFoundException { + Class clazz = super.resolveClass(classDesc); + checkWhiteList(clazz); + return clazz; + } + + }; + return objectInputStream.readObject(); + } + catch (ClassNotFoundException ex) { + throw new NestedIOException("Failed to deserialize object type", ex); + } + } + + protected void checkWhiteList(Class clazz) throws IOException { + if (this.whiteListPatterns.isEmpty()) { + return; + } + if (clazz.isArray() || clazz.isPrimitive() || clazz.equals(String.class) + || Number.class.isAssignableFrom(clazz)) { + return; + } + String className = clazz.getName(); + for (String pattern : this.whiteListPatterns) { + if (PatternMatchUtils.simpleMatch(pattern, className)) { + return; + } + } + throw new SecurityException("Attempt to deserialize unauthorized " + clazz); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/transformer/PayloadDeserializingTransformer.java b/spring-integration-core/src/main/java/org/springframework/integration/transformer/PayloadDeserializingTransformer.java index a87fa5e685..7918058d9d 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/transformer/PayloadDeserializingTransformer.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/transformer/PayloadDeserializingTransformer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,15 +16,18 @@ package org.springframework.integration.transformer; +import org.springframework.core.convert.converter.Converter; import org.springframework.core.serializer.Deserializer; -import org.springframework.core.serializer.support.DeserializingConverter; +import org.springframework.integration.support.converter.WhiteListDeserializingConverter; +import org.springframework.util.Assert; /** - * Transformer that deserializes the inbound byte array payload to an object by delegating to a - * Converter<byte[], Object>. Default delegate is a {@link DeserializingConverter} using - * Java serialization. + * Transformer that deserializes the inbound byte array payload to an object by delegating + * to a Converter<byte[], Object>. Default delegate is a + * {@link WhiteListDeserializingConverter} using Java serialization. * - *

The byte array payload must be a result of equivalent serialization. + *

+ * The byte array payload must be a result of equivalent serialization. * * @author Mark Fisher * @author Gary Russell @@ -32,15 +35,36 @@ import org.springframework.core.serializer.support.DeserializingConverter; */ public class PayloadDeserializingTransformer extends PayloadTypeConvertingTransformer { + + public PayloadDeserializingTransformer() { + doSetConverter(new WhiteListDeserializingConverter()); + } + + private void doSetConverter(Converter converter) { + this.converter = converter; + } + public void setDeserializer(Deserializer deserializer) { - this.setConverter(new DeserializingConverter(deserializer)); + setConverter(new WhiteListDeserializingConverter(deserializer)); + } + + /** + * When using a {@link WhiteListDeserializingConverter} (the default) add patterns + * for packages/classes that are allowed to be deserialized. + * A class can be fully qualified or a wildcard '*' is allowed at the + * beginning or end of the class name. + * Examples: {@code com.foo.*}, {@code *.MyClass}. + * @param patterns the patterns. + * @since 4.2.13 + */ + public void setWhiteListPatterns(String... patterns) { + Assert.isTrue(this.converter instanceof WhiteListDeserializingConverter, + "Patterns can only be provided when using a 'WhiteListDeserializingConverter'"); + ((WhiteListDeserializingConverter) this.converter).setWhiteListPatterns(patterns); } @Override protected Object transformPayload(byte[] payload) throws Exception { - if (this.converter == null) { - this.setConverter(new DeserializingConverter()); - } return this.converter.convert(payload); } diff --git a/spring-integration-core/src/main/resources/org/springframework/integration/config/spring-integration-5.0.xsd b/spring-integration-core/src/main/resources/org/springframework/integration/config/spring-integration-5.0.xsd index 4d925981fc..b2894f9ea0 100644 --- a/spring-integration-core/src/main/resources/org/springframework/integration/config/spring-integration-5.0.xsd +++ b/spring-integration-core/src/main/resources/org/springframework/integration/config/spring-integration-5.0.xsd @@ -2696,7 +2696,7 @@ - + Reference to a Deserializer instance to convert from a byte array to an object. @@ -2710,6 +2710,15 @@ + + + + When using the default Deserializer, a list of package/class patterns indicating + classes that are allowed to be deserialized. Consider providing this if you receive + data from untrusted sources. Example: "com.mycom.*, com.yourcom.*". + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/PayloadDeserializingTransformerParserTests-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/PayloadDeserializingTransformerParserTests-context.xml index d01a371b1d..38dbd6827d 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/PayloadDeserializingTransformerParserTests-context.xml +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/PayloadDeserializingTransformerParserTests-context.xml @@ -19,14 +19,17 @@ - + - + - + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/PayloadDeserializingTransformerParserTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/PayloadDeserializingTransformerParserTests.java index ef8d11e7f6..79ac812b96 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/PayloadDeserializingTransformerParserTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/PayloadDeserializingTransformerParserTests.java @@ -16,8 +16,10 @@ package org.springframework.integration.config.xml; +import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import java.io.ByteArrayOutputStream; @@ -26,15 +28,19 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.ObjectOutputStream; import java.io.Serializable; +import java.util.Set; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.core.serializer.Deserializer; +import org.springframework.integration.test.util.TestUtils; import org.springframework.integration.transformer.MessageTransformationException; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.MessageHandler; import org.springframework.messaging.PollableChannel; import org.springframework.messaging.support.GenericMessage; import org.springframework.test.context.ContextConfiguration; @@ -60,6 +66,10 @@ public class PayloadDeserializingTransformerParserTests { @Autowired private PollableChannel output; + @Autowired + @Qualifier("direct.handler") + private MessageHandler handler; + @Test public void directChannelWithSerializedStringMessage() throws Exception { @@ -69,6 +79,10 @@ public class PayloadDeserializingTransformerParserTests { assertNotNull(result); assertTrue(result.getPayload() instanceof String); assertEquals("foo", result.getPayload()); + Set patterns = TestUtils.getPropertyValue(this.handler, "transformer.converter.whiteListPatterns", + Set.class); + assertThat(patterns.size(), equalTo(1)); + assertThat(patterns.iterator().next(), equalTo("*")); } @Test diff --git a/spring-integration-core/src/test/java/org/springframework/integration/dsl/flows/IntegrationFlowTests.java b/spring-integration-core/src/test/java/org/springframework/integration/dsl/flows/IntegrationFlowTests.java index 3e5c08d714..0744596e9a 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/dsl/flows/IntegrationFlowTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/dsl/flows/IntegrationFlowTests.java @@ -26,6 +26,7 @@ import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.io.Serializable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; @@ -61,6 +62,7 @@ import org.springframework.integration.context.IntegrationContextUtils; import org.springframework.integration.dsl.IntegrationFlow; import org.springframework.integration.dsl.IntegrationFlows; import org.springframework.integration.dsl.Pollers; +import org.springframework.integration.dsl.Transformers; import org.springframework.integration.dsl.channel.MessageChannels; import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; import org.springframework.integration.handler.GenericHandler; @@ -72,7 +74,6 @@ import org.springframework.integration.store.MessageStore; import org.springframework.integration.store.SimpleMessageStore; import org.springframework.integration.support.MessageBuilder; import org.springframework.integration.support.MutableMessageBuilder; -import org.springframework.integration.transformer.PayloadDeserializingTransformer; import org.springframework.integration.transformer.PayloadSerializingTransformer; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; @@ -592,10 +593,12 @@ public class IntegrationFlowTests { .channel("foo") .fixedSubscriberChannel() .transform(Integer::parseInt) + .transform(i -> new Foo(i)) .transform(new PayloadSerializingTransformer(), c -> c.autoStartup(false).id("payloadSerializingTransformer")) .channel(MessageChannels.queue(new SimpleMessageStore(), "fooQueue")) - .transform(new PayloadDeserializingTransformer()) + .transform(Transformers.deserializer(Foo.class.getName())) + .transform(f -> f.value) .filter("true", e -> e.id("expressionFilter")) .channel(publishSubscribeChannel()) .transform((Integer p) -> p * 2, c -> c.advice(this.expressionAdvice())) @@ -863,5 +866,16 @@ public class IntegrationFlowTests { } + @SuppressWarnings("serial") + public static class Foo implements Serializable { + + private final Integer value; + + public Foo(Integer value) { + this.value = value; + } + + } + } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/transformer/PayloadDeserializingTransformerTests.java b/spring-integration-core/src/test/java/org/springframework/integration/transformer/PayloadDeserializingTransformerTests.java index 5f646db104..e6c8710d40 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/transformer/PayloadDeserializingTransformerTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/transformer/PayloadDeserializingTransformerTests.java @@ -16,8 +16,12 @@ package org.springframework.integration.transformer; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.startsWith; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; @@ -63,6 +67,31 @@ public class PayloadDeserializingTransformerTests { assertEquals(testBean.name, ((TestBean) payload).name); } + @Test + public void deserializeObjectWhiteList() throws Exception { + TestBean testBean = new TestBean("test"); + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + ObjectOutputStream objectStream = new ObjectOutputStream(byteStream); + objectStream.writeObject(testBean); + byte[] serialized = byteStream.toByteArray(); + PayloadDeserializingTransformer transformer = new PayloadDeserializingTransformer(); + transformer.setWhiteListPatterns("com.*"); + try { + transformer.transform(new GenericMessage(serialized)); + fail("expected security exception"); + } + catch (MessageTransformationException e) { + assertThat(e.getCause().getCause(), instanceOf(SecurityException.class)); + assertThat(e.getCause().getCause().getMessage(), startsWith("Attempt to deserialize unauthorized")); + } + transformer.setWhiteListPatterns("org.*"); + Message result = transformer.transform(new GenericMessage(serialized)); + Object payload = result.getPayload(); + assertNotNull(payload); + assertEquals(TestBean.class, payload.getClass()); + assertEquals(testBean.name, ((TestBean) payload).name); + } + @Test(expected = MessageTransformationException.class) public void invalidPayload() { byte[] bytes = new byte[] { 1, 2, 3 }; diff --git a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/store/JdbcChannelMessageStore.java b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/store/JdbcChannelMessageStore.java index 3f6d9aa9c9..55ba1e4e7e 100644 --- a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/store/JdbcChannelMessageStore.java +++ b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/store/JdbcChannelMessageStore.java @@ -38,7 +38,6 @@ import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.InitializingBean; import org.springframework.core.serializer.Deserializer; import org.springframework.core.serializer.Serializer; -import org.springframework.core.serializer.support.DeserializingConverter; import org.springframework.core.serializer.support.SerializingConverter; import org.springframework.dao.DuplicateKeyException; import org.springframework.integration.IntegrationMessageHeaderAccessor; @@ -51,6 +50,7 @@ import org.springframework.integration.store.MessageGroupStore; import org.springframework.integration.store.MessageStore; import org.springframework.integration.store.PriorityCapableChannelMessageStore; import org.springframework.integration.store.SimpleMessageGroupFactory; +import org.springframework.integration.support.converter.WhiteListDeserializingConverter; import org.springframework.integration.transaction.TransactionSynchronizationFactory; import org.springframework.integration.util.UUIDConverter; import org.springframework.jdbc.core.JdbcOperations; @@ -137,7 +137,7 @@ public class JdbcChannelMessageStore implements PriorityCapableChannelMessageSto private volatile JdbcTemplate jdbcTemplate; - private volatile DeserializingConverter deserializer; + private volatile WhiteListDeserializingConverter deserializer; private volatile SerializingConverter serializer; @@ -159,7 +159,7 @@ public class JdbcChannelMessageStore implements PriorityCapableChannelMessageSto * Convenient constructor for configuration use. */ public JdbcChannelMessageStore() { - this.deserializer = new DeserializingConverter(); + this.deserializer = new WhiteListDeserializingConverter(); this.serializer = new SerializingConverter(); } @@ -202,7 +202,18 @@ public class JdbcChannelMessageStore implements PriorityCapableChannelMessageSto */ @SuppressWarnings({"unchecked", "rawtypes"}) public void setDeserializer(Deserializer> deserializer) { - this.deserializer = new DeserializingConverter((Deserializer) deserializer); + this.deserializer = new WhiteListDeserializingConverter((Deserializer) deserializer); + } + + /** + * Add patterns for packages/classes that are allowed to be deserialized. A class can + * be fully qualified or a wildcard '*' is allowed at the beginning or end of the + * class name. Examples: {@code com.foo.*}, {@code *.MyClass}. + * @param patterns the patterns. + * @since 4.2.13 + */ + public void addWhiteListPatterns(String... patterns) { + this.deserializer.addWhiteListPatterns(patterns); } /** diff --git a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/store/JdbcMessageStore.java b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/store/JdbcMessageStore.java index 027803fb95..d34e2ef76b 100644 --- a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/store/JdbcMessageStore.java +++ b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/store/JdbcMessageStore.java @@ -37,7 +37,6 @@ import org.apache.commons.logging.LogFactory; import org.springframework.core.serializer.Deserializer; import org.springframework.core.serializer.Serializer; -import org.springframework.core.serializer.support.DeserializingConverter; import org.springframework.core.serializer.support.SerializingConverter; import org.springframework.dao.DuplicateKeyException; import org.springframework.integration.store.AbstractMessageGroupStore; @@ -45,6 +44,7 @@ import org.springframework.integration.store.MessageGroup; import org.springframework.integration.store.MessageMetadata; import org.springframework.integration.store.MessageStore; import org.springframework.integration.store.SimpleMessageGroup; +import org.springframework.integration.support.converter.WhiteListDeserializingConverter; import org.springframework.integration.util.UUIDConverter; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.JdbcTemplate; @@ -189,7 +189,7 @@ public class JdbcMessageStore extends AbstractMessageGroupStore implements Messa private final JdbcOperations jdbcTemplate; - private volatile DeserializingConverter deserializer; + private volatile WhiteListDeserializingConverter deserializer; private volatile SerializingConverter serializer; @@ -213,7 +213,7 @@ public class JdbcMessageStore extends AbstractMessageGroupStore implements Messa public JdbcMessageStore(JdbcOperations jdbcOperations) { Assert.notNull(jdbcOperations, "'dataSource' must not be null"); this.jdbcTemplate = jdbcOperations; - this.deserializer = new DeserializingConverter(); + this.deserializer = new WhiteListDeserializingConverter(); this.serializer = new SerializingConverter(); } @@ -265,7 +265,18 @@ public class JdbcMessageStore extends AbstractMessageGroupStore implements Messa */ @SuppressWarnings({ "unchecked", "rawtypes" }) public void setDeserializer(Deserializer> deserializer) { - this.deserializer = new DeserializingConverter((Deserializer) deserializer); + this.deserializer = new WhiteListDeserializingConverter((Deserializer) deserializer); + } + + /** + * Add patterns for packages/classes that are allowed to be deserialized. A class can + * be fully qualified or a wildcard '*' is allowed at the beginning or end of the + * class name. Examples: {@code com.foo.*}, {@code *.MyClass}. + * @param patterns the patterns. + * @since 4.2.13 + */ + public void addWhiteListPatterns(String... patterns) { + this.deserializer.addWhiteListPatterns(patterns); } @Override diff --git a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/store/channel/MessageRowMapper.java b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/store/channel/MessageRowMapper.java index 5312c99fa6..b98bbf8246 100644 --- a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/store/channel/MessageRowMapper.java +++ b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/store/channel/MessageRowMapper.java @@ -19,7 +19,7 @@ package org.springframework.integration.jdbc.store.channel; import java.sql.ResultSet; import java.sql.SQLException; -import org.springframework.core.serializer.support.DeserializingConverter; +import org.springframework.integration.support.converter.WhiteListDeserializingConverter; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.support.lob.LobHandler; import org.springframework.messaging.Message; @@ -30,20 +30,22 @@ import org.springframework.messaging.Message; * that select clause ordering is unimportant. * * @author Gunnar Hillert + * @author Gary Russell * @since 2.2 * */ public class MessageRowMapper implements RowMapper> { - private final DeserializingConverter deserializer; + private final WhiteListDeserializingConverter deserializer; private final LobHandler lobHandler; - public MessageRowMapper(DeserializingConverter deserializer, LobHandler lobHandler) { + public MessageRowMapper(WhiteListDeserializingConverter deserializer, LobHandler lobHandler) { this.deserializer = deserializer; this.lobHandler = lobHandler; } + @Override public Message mapRow(ResultSet rs, int rowNum) throws SQLException { return (Message) this.deserializer.convert(this.lobHandler.getBlobAsBytes(rs, "MESSAGE_BYTES")); } diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/store/MongoDbMessageStore.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/store/MongoDbMessageStore.java index 14ec8e8570..76feaf74a7 100644 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/store/MongoDbMessageStore.java +++ b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/store/MongoDbMessageStore.java @@ -38,7 +38,6 @@ import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.core.convert.converter.Converter; -import org.springframework.core.serializer.support.DeserializingConverter; import org.springframework.core.serializer.support.SerializingConverter; import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Transient; @@ -71,6 +70,7 @@ import org.springframework.integration.store.MessageStore; import org.springframework.integration.store.SimpleMessageGroup; import org.springframework.integration.support.MutableMessage; import org.springframework.integration.support.MutableMessageBuilder; +import org.springframework.integration.support.converter.WhiteListDeserializingConverter; import org.springframework.jmx.export.annotation.ManagedAttribute; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHeaders; @@ -144,6 +144,8 @@ public class MongoDbMessageStore extends AbstractMessageGroupStore private ApplicationContext applicationContext; + private String[] whiteListPatterns; + /** * Create a MongoDbMessageStore using the provided {@link MongoDbFactory}.and the default collection name. @@ -177,6 +179,16 @@ public class MongoDbMessageStore extends AbstractMessageGroupStore this.applicationContext = applicationContext; } + /** + * Add patterns for packages/classes that are allowed to be deserialized. A class can + * be fully qualified or a wildcard '*' is allowed at the beginning or end of the + * class name. Examples: {@code com.foo.*}, {@code *.MyClass}. + * @param patterns the patterns. + */ + public void addWhiteListPatterns(String... patterns) { + this.whiteListPatterns = patterns; + } + @Override public void afterPropertiesSet() throws Exception { if (this.applicationContext != null) { @@ -504,7 +516,12 @@ public class MongoDbMessageStore extends AbstractMessageGroupStore customConverters.add(new MessageHistoryToDocumentConverter()); customConverters.add(new DocumentToGenericMessageConverter()); customConverters.add(new DocumentToMutableMessageConverter()); - customConverters.add(new DocumentToErrorMessageConverter()); + DocumentToErrorMessageConverter docToErrorMessageConverter = new DocumentToErrorMessageConverter(); + if (MongoDbMessageStore.this.whiteListPatterns != null) { + docToErrorMessageConverter.deserializingConverter + .addWhiteListPatterns(MongoDbMessageStore.this.whiteListPatterns); + } + customConverters.add(docToErrorMessageConverter); customConverters.add(new DocumentToAdviceMessageConverter()); customConverters.add(new ThrowableToBytesConverter()); this.setCustomConversions(new MongoCustomConversions(customConverters)); @@ -730,7 +747,7 @@ public class MongoDbMessageStore extends AbstractMessageGroupStore @ReadingConverter private class DocumentToErrorMessageConverter implements Converter { - private final Converter deserializingConverter = new DeserializingConverter(); + private final WhiteListDeserializingConverter deserializingConverter = new WhiteListDeserializingConverter(); DocumentToErrorMessageConverter() { super(); diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/support/BinaryToMessageConverter.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/support/BinaryToMessageConverter.java index 1dd692edfc..02a4283cc3 100644 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/support/BinaryToMessageConverter.java +++ b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/support/BinaryToMessageConverter.java @@ -19,22 +19,33 @@ package org.springframework.integration.mongodb.support; import org.bson.types.Binary; import org.springframework.core.convert.converter.Converter; -import org.springframework.core.serializer.support.DeserializingConverter; import org.springframework.data.convert.ReadingConverter; +import org.springframework.integration.support.converter.WhiteListDeserializingConverter; import org.springframework.messaging.Message; /** * @author Artem Bilan + * @author Gary Russell * @since 5.0 */ @ReadingConverter public class BinaryToMessageConverter implements Converter> { - private final Converter deserializingConverter = new DeserializingConverter(); + private final WhiteListDeserializingConverter deserializingConverter = new WhiteListDeserializingConverter(); @Override public Message convert(Binary source) { return (Message) this.deserializingConverter.convert(source.getData()); } + /** + * Add patterns for packages/classes that are allowed to be deserialized. A class can + * be fully qualified or a wildcard '*' is allowed at the beginning or end of the + * class name. Examples: {@code com.foo.*}, {@code *.MyClass}. + * @param patterns the patterns. + */ + public void addWhiteListPatterns(String... patterns) { + this.deserializingConverter.addWhiteListPatterns(patterns); + } + } diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/support/MongoDbMessageBytesConverter.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/support/MongoDbMessageBytesConverter.java index a994d38c3b..9ce5550535 100644 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/support/MongoDbMessageBytesConverter.java +++ b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/support/MongoDbMessageBytesConverter.java @@ -24,10 +24,10 @@ import org.bson.types.Binary; import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.GenericConverter; -import org.springframework.core.serializer.support.DeserializingConverter; import org.springframework.core.serializer.support.SerializingConverter; import org.springframework.data.convert.ReadingConverter; import org.springframework.data.convert.WritingConverter; +import org.springframework.integration.support.converter.WhiteListDeserializingConverter; import org.springframework.messaging.Message; /** @@ -36,6 +36,7 @@ import org.springframework.messaging.Message; * And vice versa - to convert {@link byte[]} from the MongoDB to the {@link Message}. * @author Artem Bilan + * @author Gary Russell * @since 4.2.10 * @deprecated since 5.0 in favor of {@link MessageToBinaryConverter} and {@link BinaryToMessageConverter} */ @@ -46,7 +47,7 @@ public class MongoDbMessageBytesConverter implements GenericConverter { private final Converter serializingConverter = new SerializingConverter(); - private final Converter deserializingConverter = new DeserializingConverter(); + private final WhiteListDeserializingConverter deserializingConverter = new WhiteListDeserializingConverter(); @Override public Set getConvertibleTypes() { @@ -66,4 +67,14 @@ public class MongoDbMessageBytesConverter implements GenericConverter { } } + /** + * Add patterns for packages/classes that are allowed to be deserialized. A class can + * be fully qualified or a wildcard '*' is allowed at the beginning or end of the + * class name. Examples: {@code com.foo.*}, {@code *.MyClass}. + * @param patterns the patterns. + */ + public void addWhiteListPatterns(String... patterns) { + this.deserializingConverter.addWhiteListPatterns(patterns); + } + } diff --git a/src/reference/asciidoc/transformer.adoc b/src/reference/asciidoc/transformer.adoc index d4e718ef01..51118bc627 100644 --- a/src/reference/asciidoc/transformer.adoc +++ b/src/reference/asciidoc/transformer.adoc @@ -116,9 +116,13 @@ These will use standard Java serialization by default, but you can provide an im ---- - + ---- +IMPORTANT: When deserializing data from untrusted sources, you should consider adding a `white-list` of package/class patterns. +By default, all classes will be deserialized. + ====== Object-to-Map and Map-to-Object Transformers Spring Integration also provides _Object-to-Map_ and _Map-to-Object_ transformers which utilize the JSON to serialize and de-serialize the object graphs.