diff --git a/src/main/java/com/fasterxml/jackson/databind/exc/InsecureTypeMatchException.java b/src/main/java/com/fasterxml/jackson/databind/exc/InsecureTypeMatchException.java new file mode 100644 index 0000000000..1de872b19e --- /dev/null +++ b/src/main/java/com/fasterxml/jackson/databind/exc/InsecureTypeMatchException.java @@ -0,0 +1,14 @@ +package com.fasterxml.jackson.databind.exc; + + +/** + * Exception used when flagging a Matcher that will allow all subtypes of Object.class or Serializable.class + * As allowing such a wide array of classes to be deserialized will open the application up to security vulnerabilities + * and so should be avoided. + */ +public class InsecureTypeMatchException extends IllegalArgumentException { + + public InsecureTypeMatchException(String msg) { + super(msg); + } +} diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/BasicPolymorphicTypeValidator.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/BasicPolymorphicTypeValidator.java index 23f9d81b1e..748c67458d 100644 --- a/src/main/java/com/fasterxml/jackson/databind/jsontype/BasicPolymorphicTypeValidator.java +++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/BasicPolymorphicTypeValidator.java @@ -1,11 +1,13 @@ package com.fasterxml.jackson.databind.jsontype; +import java.io.Serializable; import java.util.*; import java.util.regex.Pattern; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.cfg.MapperConfig; +import com.fasterxml.jackson.databind.exc.InsecureTypeMatchException; /** * Standard {@link BasicPolymorphicTypeValidator} implementation that users may want @@ -64,6 +66,10 @@ protected abstract static class NameMatcher { * rules are checked. */ public static class Builder { + + private static final List> invalidBaseClasses = Arrays.asList(Serializable.class,Object.class); + + protected Set> _invalidBaseTypes; /** @@ -84,6 +90,8 @@ public static class Builder { protected Builder() { } + + // // Methods for checking solely by base type (before subtype even considered) /** @@ -239,7 +247,16 @@ public boolean match(String clazzName) { }); } - public BasicPolymorphicTypeValidator build() { + /** + * Validates and Constructs the BasicPolymorphicTypeValidator. + * Unlike build() no check is done to ensure the Matchers added to this validator will allow Object.class + * or Serializable.class and their subtypes to be deserialised. + * As the name suggests, this is an insecure operation. Please think very carefully before calling this method + * rather than build(), especially if the caller is outside the trust boundary of this application. As doing + * so will leave your application open to vulnerabilities including remote code execution. + * @return the BasicPolymorphicTypeValidator. + */ + public BasicPolymorphicTypeValidator build_insecure() { return new BasicPolymorphicTypeValidator(_invalidBaseTypes, (_baseTypeMatchers == null) ? null : _baseTypeMatchers.toArray(new TypeMatcher[0]), (_subTypeNameMatchers == null) ? null : _subTypeNameMatchers.toArray(new NameMatcher[0]), @@ -247,6 +264,58 @@ public BasicPolymorphicTypeValidator build() { ); } + /** + * Validates and Constructs the BasicPolymorphicTypeValidator. + * If a Matcher added to this Validator will allow Object.class or Serializable.class and all of their sub types + * to be deserialised a InsecureTypeMatchException will be thrown. + * This is due to the inherent security problems of allowing either all classes on the class path to be + * arbitrarily deserialised in the case of Object.class or large amounts of classes in the case of + * Serializable.class. + * If you really need to allow either of these blocked classes. Use build_insecure(). However please be aware + * of the very real security problems you are introducing into your application by doing so. + * @return the BasicPolymorphicTypeValidator. + * @throws InsecureTypeMatchException if any matcher added will allow Object.class, Serializable.class + */ + public BasicPolymorphicTypeValidator build() throws InsecureTypeMatchException { + validateTypeMatch(); + return build_insecure(); + } + + /** + * validates that each of the Matchers will not allow Serializable.class or Object.class. + * @throws InsecureTypeMatchException if an included Matcher will allow one or both of these classes through. + */ + private void validateTypeMatch() throws InsecureTypeMatchException { + if(_baseTypeMatchers!=null) { + for (TypeMatcher tm : _baseTypeMatchers) { + for (Class klass : invalidBaseClasses) { + if (tm.match(klass)) { + throw new InsecureTypeMatchException("Insecure Base class found : " + klass.getName()); + } + } + } + } + if(_subTypeNameMatchers!=null) { + for (NameMatcher nm : _subTypeNameMatchers) { + for (Class klass : invalidBaseClasses) { + if (nm.match(klass.getName())) { + throw new InsecureTypeMatchException("Insecure Base class found : " + klass.getName()); + } + } + } + } + if(_subTypeClassMatchers!=null) { + for (TypeMatcher tm : _subTypeClassMatchers) { + for (Class klass : invalidBaseClasses) { + if (tm.match(klass)) { + throw new InsecureTypeMatchException("Insecure Base class found : " + klass.getName()); + } + } + } + } + + } + protected Builder _appendBaseMatcher(TypeMatcher matcher) { if (_baseTypeMatchers == null) { _baseTypeMatchers = new ArrayList<>(); diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/vld/BasicPTVTest.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/vld/BasicPTVTest.java index 659cf4936f..f1a28a3941 100644 --- a/src/test/java/com/fasterxml/jackson/databind/jsontype/vld/BasicPTVTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/vld/BasicPTVTest.java @@ -1,9 +1,13 @@ package com.fasterxml.jackson.databind.jsontype.vld; +import java.io.Serializable; +import java.util.Arrays; +import java.util.List; import java.util.regex.Pattern; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping; +import com.fasterxml.jackson.databind.exc.InsecureTypeMatchException; import com.fasterxml.jackson.databind.exc.InvalidDefinitionException; import com.fasterxml.jackson.databind.exc.InvalidTypeIdException; import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator; @@ -37,6 +41,10 @@ public ValueB(int x) { } } + static class ValueC extends ValueB implements Serializable {} + static class ValueD extends Object {} + + // // // Value types // make this type `final` to avoid polymorphic handling @@ -262,6 +270,211 @@ public void testAllowBySubClassPattern() throws Exception { verifyException(e, "as a subtype of"); } } + + + public void testBlockSerializableClass() throws Exception { + try { + BasicPolymorphicTypeValidator.builder() + .allowIfSubType(Serializable.class) + .build(); + fail("no exception was thrown when allowIfSubType Serializable.class"); + } catch (InsecureTypeMatchException tme) { + assertEquals("Insecure Base class found : java.io.Serializable",tme.getMessage()); + } + } + + public void testBlockObjectClass() throws Exception { + try { + BasicPolymorphicTypeValidator.builder() + .allowIfSubType(Object.class) + .build(); + fail("no exception was thrown when allowIfSubType Object.class"); + } catch (InsecureTypeMatchException tme) { + assertEquals("Insecure Base class found : java.io.Serializable",tme.getMessage()); + } + } + + + + public void testBlockBaseTypeMatchSerializable() throws Exception { + try { + BasicPolymorphicTypeValidator.builder() + .allowIfBaseType(Object.class) + .build(); + fail("no exception was thrown when allowIfSubType Object.class"); + } catch (InsecureTypeMatchException tme) { + assertEquals("Insecure Base class found : java.io.Serializable",tme.getMessage()); + } + } + + public void testBlockBaseTypeMatchObject() throws Exception { + try { + BasicPolymorphicTypeValidator.builder() + .allowIfBaseType(Object.class) + .build(); + fail("no exception was thrown when allowIfSubType Object.class"); + } catch (InsecureTypeMatchException tme) { + assertEquals("Insecure Base class found : java.io.Serializable",tme.getMessage()); + } + } + + public void testBlockSubTypeMatchObject() throws Exception { + try { + BasicPolymorphicTypeValidator.builder() + .allowIfSubType(Object.class.getName()) + .build(); + fail("no exception was thrown when allowIfSubType Object.class"); + } catch (InsecureTypeMatchException tme) { + assertEquals("Insecure Base class found : java.lang.Object",tme.getMessage()); + } + } + + + + public void testBlockSubTypeMatchSerializable() throws Exception { + try { + BasicPolymorphicTypeValidator.builder() + .allowIfSubType(Serializable.class.getName()) + .build(); + fail("no exception was thrown when allowIfSubType Object.class"); + } catch (InsecureTypeMatchException tme) { + assertEquals("Insecure Base class found : java.io.Serializable",tme.getMessage()); + } + } + + + public void testNotBlockValidBaseTypesByClassName() throws Exception { + List> validClasses = Arrays.asList(ValueB.class,ValueC.class,ValueD.class); + for(Class klass : validClasses) { + try { + BasicPolymorphicTypeValidator.builder() + .allowIfBaseType(klass.getName()) + .build(); + } catch (InsecureTypeMatchException tme) { + fail("Class "+klass.getName()+" was blocked"); + } + } + } + + public void testNotBlockValidBaseTypesByClass() { + List> validClasses = Arrays.asList(ValueB.class,ValueC.class,ValueD.class); + for(Class klass : validClasses) { + try { + BasicPolymorphicTypeValidator.builder() + .allowIfBaseType(klass) + .build(); + } catch (InsecureTypeMatchException tme) { + fail("Class "+klass.getName()+" was blocked"); + } + } + } + + public void testNotBlockValidSubTypesByClass() throws Exception { + List> validClasses = Arrays.asList(ValueB.class,ValueC.class,ValueD.class); + for(Class klass : validClasses) { + try { + BasicPolymorphicTypeValidator.builder() + .allowIfSubType(klass) + .build(); + } catch (InsecureTypeMatchException tme) { + fail("Class "+klass.getName()+" was blocked"); + } + } + } + + public void testNotBlockValidSubTypesByName() throws Exception { + List> validClasses = Arrays.asList(ValueB.class,ValueC.class,ValueD.class); + for(Class klass : validClasses) { + try { + BasicPolymorphicTypeValidator.builder() + .allowIfSubType(klass.getName()) + .build(); + } catch (InsecureTypeMatchException tme) { + fail("Class "+klass.getName()+" was blocked"); + } + } + } + + public void testNotBlockValidSubTypesByNameWhenBuildingInsecurely() throws Exception { + List> validClasses = Arrays.asList(ValueB.class,ValueC.class,ValueD.class); + for(Class klass : validClasses) { + try { + BasicPolymorphicTypeValidator.builder() + .allowIfSubType(klass.getName()) + .build_insecure(); + } catch (InsecureTypeMatchException tme) { + fail("Class "+klass.getName()+" was blocked"); + } + } + } + + public void testNotBlockValidBaseTypesByClassWhenBuildingInsecurely() { + List> validClasses = Arrays.asList(ValueB.class,ValueC.class,ValueD.class); + for(Class klass : validClasses) { + try { + BasicPolymorphicTypeValidator.builder() + .allowIfBaseType(klass) + .build_insecure(); + } catch (InsecureTypeMatchException tme) { + fail("Class "+klass.getName()+" was blocked"); + } + } + } + + public void testNotBlockValidSubTypesByClassWhenBuildingInsecurely() throws Exception { + List> validClasses = Arrays.asList(ValueB.class,ValueC.class,ValueD.class); + for(Class klass : validClasses) { + try { + BasicPolymorphicTypeValidator.builder() + .allowIfSubType(klass) + .build_insecure(); + } catch (InsecureTypeMatchException tme) { + fail("Class "+klass.getName()+" was blocked"); + } + } + } + + public void testNotBlockInvalidSubTypesByNameWhenBuildingInsecurely() throws Exception { + List> invalidClasses = Arrays.asList(Object.class,Serializable.class); + for(Class klass : invalidClasses) { + try { + BasicPolymorphicTypeValidator.builder() + .allowIfSubType(klass.getName()) + .build_insecure(); + } catch (InsecureTypeMatchException tme) { + fail("Class "+klass.getName()+" was blocked"); + } + } + } + + public void testNotBlockInvalidBaseTypesByClassWhenBuildingInsecurely() { + List> invalidClasses = Arrays.asList(Object.class,Serializable.class); + for(Class klass : invalidClasses) { + try { + BasicPolymorphicTypeValidator.builder() + .allowIfBaseType(klass) + .build_insecure(); + } catch (InsecureTypeMatchException tme) { + fail("Class "+klass.getName()+" was blocked"); + } + } + } + + public void testNotBlockInvalidSubTypesByClassWhenBuildingInsecurely() throws Exception { + List> invalidClasses = Arrays.asList(Object.class,Serializable.class); + for(Class klass : invalidClasses) { + try { + BasicPolymorphicTypeValidator.builder() + .allowIfSubType(klass) + .build_insecure(); + } catch (InsecureTypeMatchException tme) { + fail("Class "+klass.getName()+" was blocked"); + } + } + } + + + }