Skip to content

Commit

Permalink
Add basic protection against untrusted deserialization.
Browse files Browse the repository at this point in the history
  • Loading branch information
kwart committed Mar 28, 2018
1 parent 8704aaf commit 1c5a63e
Show file tree
Hide file tree
Showing 27 changed files with 1,147 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,13 @@
</xs:complexType>
</xs:element>
<xs:element name="check-class-def-errors" type="xs:boolean" minOccurs="0" maxOccurs="1" default="true"/>
<xs:element name="java-serialization-filter" type="java-serialization-filter" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>
Basic protection against untrusted deserialization based on class/package blacklisting and whitelisting.
</xs:documentation>
</xs:annotation>
</xs:element>
</xs:all>
</xs:complexType>
<xs:complexType name="serialization-factory">
Expand Down Expand Up @@ -530,6 +537,44 @@
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="java-serialization-filter">
<xs:all>
<xs:element name="blacklist" type="filter-list" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>
Blacklisted classes and packages, which are not allowed to be deserialized.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="whitelist" type="filter-list" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>
Whitelisted classes and packages, which are allowed to be deserialized. If the list is empty
(no class or package name provided) then all classes are allowed.
</xs:documentation>
</xs:annotation>
</xs:element>
</xs:all>
</xs:complexType>
<xs:complexType name="filter-list">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="class" type="xs:string" minOccurs="0" maxOccurs="unbounded">
<xs:annotation>
<xs:documentation>
Name of a class to be included in the list.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="package" type="xs:string" minOccurs="0" maxOccurs="unbounded">
<xs:annotation>
<xs:documentation>
Name of a package to be included in the list.
</xs:documentation>
</xs:annotation>
</xs:element>
</xs:choice>
</xs:complexType>

<xs:complexType name="socket-interceptor">
<xs:all>
<xs:element name="class-name" type="xs:string" minOccurs="0" maxOccurs="1"/>
Expand Down
11 changes: 11 additions & 0 deletions hazelcast-client/src/main/resources/hazelcast-client-full.xml
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,17 @@
class-name="com.hazelcast.examples.SerializerFactory"/>
</serializers>
<check-class-def-errors>true</check-class-def-errors>
<java-serialization-filter>
<blacklist>
<class>com.acme.app.BeanComparator</class>
</blacklist>
<whitelist>
<class>java.lang.String</class>
<class>example.Foo</class>
<package>com.acme.app</package>
<package>com.acme.app.subpkg</package>
</whitelist>
</java-serialization-filter>
</serialization>

<native-memory enabled="false" allocator-type="POOLED">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@
package com.hazelcast.spring;

import com.hazelcast.config.AbstractXmlConfigHelper;
import com.hazelcast.config.ClassFilterList;
import com.hazelcast.config.DiscoveryConfig;
import com.hazelcast.config.DiscoveryStrategyConfig;
import com.hazelcast.config.EvictionConfig;
import com.hazelcast.config.EvictionPolicy;
import com.hazelcast.config.GlobalSerializerConfig;
import com.hazelcast.config.InvalidConfigurationException;
import com.hazelcast.config.JavaSerializationFilterConfig;
import com.hazelcast.config.NearCachePreloaderConfig;
import com.hazelcast.config.SerializationConfig;
import com.hazelcast.config.SerializerConfig;
Expand All @@ -35,6 +37,7 @@
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.support.ManagedSet;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.NamedNodeMap;
Expand Down Expand Up @@ -306,6 +309,8 @@ protected void handleSerialization(Node node) {
handlePortableFactories(child, serializationConfigBuilder);
} else if ("serializers".equals(nodeName)) {
handleSerializers(child, serializationConfigBuilder);
} else if ("java-serialization-filter".equals(nodeName)) {
handleJavaSerializationFilter(child, serializationConfigBuilder);
}
}
configBuilder.addPropertyValue("serializationConfig", beanDefinition);
Expand Down Expand Up @@ -511,5 +516,37 @@ private void handleDiscoveryStrategy(Node node, ManagedList<BeanDefinition> disc
}
discoveryStrategyConfigs.add(discoveryStrategyConfigBuilder.getBeanDefinition());
}

protected void handleJavaSerializationFilter(final Node node, BeanDefinitionBuilder serializationConfigBuilder) {
BeanDefinitionBuilder filterConfigBuilder = createBeanBuilder(JavaSerializationFilterConfig.class);
ManagedList<BeanDefinition> discoveryStrategyConfigs = new ManagedList<BeanDefinition>();
for (Node child : childElements(node)) {
String name = cleanNodeName(child);
if ("blacklist".equals(name)) {
filterConfigBuilder.addPropertyValue("blacklist", createFilterListBean(child));
} else if ("whitelist".equals(name)) {
filterConfigBuilder.addPropertyValue("whitelist", createFilterListBean(child));
}
}
serializationConfigBuilder.addPropertyValue("javaSerializationFilterConfig",
filterConfigBuilder.getBeanDefinition());
}

private AbstractBeanDefinition createFilterListBean(Node node) {
BeanDefinitionBuilder filterListBuilder = createBeanBuilder(ClassFilterList.class);
ManagedSet<String> classes = new ManagedSet<String>();
ManagedSet<String> packages = new ManagedSet<String>();
for (Node child : childElements(node)) {
final String name = cleanNodeName(child);
if ("class".equals(name)) {
classes.add(getTextContent(child));
} else if ("package".equals(name)) {
packages.add(getTextContent(child));
}
}
filterListBuilder.addPropertyValue("classes", classes);
filterListBuilder.addPropertyValue("packages", packages);
return filterListBuilder.getBeanDefinition();
}
}
}
41 changes: 41 additions & 0 deletions hazelcast-spring/src/main/resources/hazelcast-spring-3.10.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -1753,6 +1753,46 @@
</xs:complexType>
</xs:element>

<xs:element name="java-serialization-filter">
<xs:complexType>
<xs:sequence>
<xs:element name="blacklist" type="filter-list" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>
Blacklist used for deserialization class filtering.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="whitelist" type="filter-list" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>
Blacklist used for deserialization class filtering.
</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>

<xs:complexType name="filter-list">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="class" type="xs:string" minOccurs="0" maxOccurs="unbounded">
<xs:annotation>
<xs:documentation>
Name of a class to be included in the list.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="package" type="xs:string" minOccurs="0" maxOccurs="unbounded">
<xs:annotation>
<xs:documentation>
Name of a package to be included in the list.
</xs:documentation>
</xs:annotation>
</xs:element>
</xs:choice>
</xs:complexType>

<xs:complexType name="serialization-factory">
<xs:attributeGroup ref="class-or-bean-name"/>
<xs:attribute name="factory-id" type="xs:string" use="required"/>
Expand Down Expand Up @@ -2916,6 +2956,7 @@
<xs:element ref="data-serializable-factories" minOccurs="0" maxOccurs="1"/>
<xs:element ref="portable-factories" minOccurs="0" maxOccurs="1"/>
<xs:element ref="serializers" minOccurs="0" maxOccurs="1"/>
<xs:element ref="java-serialization-filter" minOccurs="0" maxOccurs="1"/>
</xs:sequence>
<xs:attribute name="use-native-byte-order" use="optional" type="xs:string" default="false"/>
<xs:attribute name="byte-order" use="optional" default="BIG_ENDIAN">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.hazelcast.config.CacheDeserializedValues;
import com.hazelcast.config.CacheSimpleConfig;
import com.hazelcast.config.CardinalityEstimatorConfig;
import com.hazelcast.config.ClassFilterList;
import com.hazelcast.config.Config;
import com.hazelcast.config.CountDownLatchConfig;
import com.hazelcast.config.DiscoveryConfig;
Expand All @@ -40,6 +41,7 @@
import com.hazelcast.config.IcmpFailureDetectorConfig;
import com.hazelcast.config.InMemoryFormat;
import com.hazelcast.config.ItemListenerConfig;
import com.hazelcast.config.JavaSerializationFilterConfig;
import com.hazelcast.config.ListConfig;
import com.hazelcast.config.ListenerConfig;
import com.hazelcast.config.LockConfig;
Expand Down Expand Up @@ -1279,4 +1281,25 @@ public void testExplicitPortCountConfiguration() {

assertEquals(42, portCount);
}

@Test
public void testJavaSerializationFilterConfig() {
JavaSerializationFilterConfig filterConfig = config.getSerializationConfig().getJavaSerializationFilterConfig();
assertNotNull(filterConfig);

ClassFilterList blacklist = filterConfig.getBlacklist();
assertNotNull(blacklist);
assertEquals(1, blacklist.getClasses().size());
assertTrue(blacklist.getClasses().contains("com.acme.app.BeanComparator"));
assertEquals(0, blacklist.getPackages().size());

ClassFilterList whitelist = filterConfig.getWhitelist();
assertNotNull(whitelist);
assertEquals(2, whitelist.getClasses().size());
assertTrue(whitelist.getClasses().contains("java.lang.String"));
assertTrue(whitelist.getClasses().contains("example.Foo"));
assertEquals(2, whitelist.getPackages().size());
assertTrue(whitelist.getPackages().contains("com.acme.app"));
assertTrue(whitelist.getPackages().contains("com.acme.app.subpkg"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,17 @@
<hz:serializer type-class="com.hazelcast.spring.serialization.DummySerializableObject2"
implementation="dummySerializer"/>
</hz:serializers>
<hz:java-serialization-filter>
<hz:blacklist>
<hz:class>com.acme.app.BeanComparator</hz:class>
</hz:blacklist>
<hz:whitelist>
<hz:class>java.lang.String</hz:class>
<hz:class>example.Foo</hz:class>
<hz:package>com.acme.app</hz:package>
<hz:package>com.acme.app.subpkg</hz:package>
</hz:whitelist>
</hz:java-serialization-filter>
</hz:serialization>

<hz:native-memory enabled="false" allocator-type="POOLED" metadata-space-percentage="10.2"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,8 @@ protected SerializationConfig parseSerialization(final Node node) {
fillPortableFactories(child, serializationConfig);
} else if ("serializers".equals(name)) {
fillSerializers(child, serializationConfig);
} else if ("java-serialization-filter".equals(name)) {
fillJavaSerializationFilter(child, serializationConfig);
}
}
return serializationConfig;
Expand Down Expand Up @@ -510,6 +512,34 @@ protected void fillSerializers(final Node node, SerializationConfig serializatio
}
}

protected void fillJavaSerializationFilter(final Node node, SerializationConfig serializationConfig) {
JavaSerializationFilterConfig filterConfig = new JavaSerializationFilterConfig();
serializationConfig.setJavaSerializationFilterConfig(filterConfig);
for (Node child : childElements(node)) {
final String name = cleanNodeName(child);
if ("blacklist".equals(name)) {
ClassFilterList list = parseClassFilterList(child);
filterConfig.setBlacklist(list);
} else if ("whitelist".equals(name)) {
ClassFilterList list = parseClassFilterList(child);
filterConfig.setWhitelist(list);
}
}
}

private ClassFilterList parseClassFilterList(Node node) {
ClassFilterList list = new ClassFilterList();
for (Node child : childElements(node)) {
final String name = cleanNodeName(child);
if ("class".equals(name)) {
list.addClasses(getTextContent(child));
} else if ("package".equals(name)) {
list.addPackages(getTextContent(child));
}
}
return list;
}

@SuppressFBWarnings("DM_BOXED_PRIMITIVE_FOR_PARSING")
protected void fillNativeMemoryConfig(Node node, NativeMemoryConfig nativeMemoryConfig) {
final NamedNodeMap atts = node.getAttributes();
Expand Down
Loading

0 comments on commit 1c5a63e

Please sign in to comment.