Jackson 2.10: Safe Default Typing

aka stemming the flow of Jackson-databind CVEs

@cowtowncoder
5 min readOct 22, 2019

(note: this story is part of deeper dive of Jackson 2.10 release features, after Overview of Jackson 2.10)

Arguably the most important new/improved feature in Jackson 2.10 is so-called “Safe Default Typing”. It is the replacement of existing basic unbounded Default Typing, problems of which are explained in earlier “On Jackson CVEs: Don’t Panic!” blog post.

What is Safe Default Typing?

Safe Default Typing means that in addition to specifying the main existing criteria for default typing — which classes should automatically use Polymorphic Type handling based on class name (similar to using “@JsonTypeInfo” annotation, but applying to groups of classes) — developers will now be required to also specify validator of type PolymorphicTypeValidator that will determine if deserialization of given class name is (or is not) allowed.

How is Safe Default Typing enabled?

New methods were added in Jackson 2.10 supporting both the old style enabling via ObjectMapper itself (something that will be removed from 3.0):

ObjectMapper mapper = new ObjectMapper();
PolymorphicTypeValidator ptv = ...; // see examples below
mapper.activateDefaultTyping(DefaultTyping.NON_FINAL, ptv);

as well as new builder-style construction (added in 2.10, only mechanism in 3.0+):

ObjectMapper mapper = JsonMapper.builder()
.activateDefaultTyping(DefaultTyping.NON_FINAL, ptv)
.build();

Regardless of method, resulting ObjectMapper in this case would add class name as type id for all properties of type that is not final , similar to earlier Default Typing. The only difference is the use of explicit validator.

Note, too, that 2.10 deprecates unsafe variants named enableDefaultTyping() (and 3.0 will remove them for good).

Where do I get a PolymorphicTypeValidator

While it is possible to directly extend abstract PolymorphicTypeValidator (it only has 3 methods), or, ever simpler, PolymorphicTypeValidator.Base which has empty implementation for all methods, most of the time you will want to use BasicPolymorphicTypeValidator implementation. For example:

PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator
.builder()
.allowIfBaseType(MyThing.class)
.build();

which would allow polymorphic deserialization of all values of properties of type MyThing (or its subtypes).

Note that evaluation order for rules in same category (for base type, or for sub types) are evaluated in order added: first rule first, last rule last. This mostly matters if you want to apply “denyForExactBaseType()” rule, which would typically come before allow rules (it may be used, for example, to deny use of java.lang.Object as base type for any polymorphic values).

When and how is validation applied?

Before going into more useful examples of BasicPolymorphicTypeValidator, it is useful to consider when are validators actually called. This makes it easier to explain how to configure one you use (or, if necessary, fully implement).

Validators are called during multiple different but related phases:

  1. When a Bean property definition is encountered for type that is to use Default Typing (typically when constructing a BeanDeserializer): validateBaseType() is called. This (like all methods called) may return one of Validity result values ( ALLOWED, DENIED or INDETERMINATE) or throw an exception (which effectively another way to indicate DENIED but allows customizing of error message)
  2. When an actual Type Id (class name) is first resolved into Class: validateSubClassName() is called
  3. Before locating a deserializer for resolved Class : validateSubType() is called

Of these, (1) is always called first, once per property definition: (2) is called once per distinct subclass for actual values if (1) does not return determinate result. If (2) does not return determinate result, (3) is called: and finally, if even that is indeterminate, resolution is denied (i.e. default empty implementation would NOT allow any subclasses) and a JsonMappingException is thrown with brief mention that subtype resolution was not allowed.

Example validation: basic inclusion

Consider following Bean class definitions:

public final class MyPolyBean {
public MyValue value;
}
public abstract class MyValue {
}
// and some implementations like
public class MySubType extends MyValue {
public int x;
}

Assuming configuration using DefaultTyping.NON_FINAL, serializing an instance of MyPolyBean we would get JSON output like

{ "value" : [ "MySubType", { "x", 3 } ] }

(note that no Type Id is used for MyPolyBean as it is final)

When deserializing, Jackson would notice that declared base type of “value” is MyValue and would call method PolymorphicTypeValidator.validateBaseType(). There are 3 result values:

  1. Validity.ALLOWED : allow all values of property since base type itself is validated
  2. Validity.DENIED: do not allow deserialization of property at all (base type itself considered unsafe). Sometimes used to prevent use of fundamentally unsafe types such as java.lang.Object or java.io.Serializable
  3. Validity.INDETERMINATE: no conclusion can be drawn on base type, need to validate actual subclass id

Of these, the last choice is most common. Let’s now consider this validator:

PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator
.builder()
.allowIfBaseType(MyValue.class)
.build();

in this case, call to validateBaseType would return Validity.ALLOWED and deserialization of all legal values would proceed without further checks.

But we could have chosen a different legal validator as well:

PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator
.builder()
.allowIfSubType(MySubType.class)
.build();

in which case first check would return Validity.INDETERMINATE, and a further call would be made, first with class name “MySubType”, and since that is not defined for match, after resolving to MySubType.class, finally call that does match allowed class we indicate.

Note that there are also variations for both base and subtypes that allow use of class name: allowIfBaseType(Pattern) (name matches regular expression) /allowIfBaseType(String) (name has specified prefix, usually package name) and allowIfSubType(Pattern) / allowIfSubType(String):

PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator
.builder()
.allowIfBaseType("com.mycorporation.")
.build();

Example validation: more advanced

Above examples may work well for simple cases where you have shared base class for all of your values. But sometimes you do not have that. For example you might have generic wrapped

class Wrapper<T> {
public T value;
}

in which case base type would be T which (without further bounds) must be introspected as just java.lang.Object. We do NOT want to just allow allObject.class subtypes — that is literally anything, and the reason why there were potential attacks using Default Typing! — but need something bit more sophisticated.

One possibility is that values we have consist of a small number of disjoint types. For example, we could use:

PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator
.builder()
.allowIfSubType(Map.class)
.allowIfSubType(List.class)
.allowIfSubType("com.myworkplace.")
.allowIfSubType(Number.class)
.allowIfSubType(SpecialValue.class)
.build();

where “subtype” rules must be used as base type (Object) is too general to allow determination.

PolymorphicTypeValidator with “@JsonTypeInfo”

In addition to being used for Safe Default Typing, PolymorphicTypeValidator should also be used for those use cases of JsonTypeInfo that uses potentially unsafe base type, such as java.lang.Object or java.io.Serializable. It can be configured using either old style (directly on ObjectMapper) or new builder style:

// if doing this, you better configure validator!
class ValueWrapper {
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
public Object value;
}
PolymorphicTypeValidator ptv = ....;
ObjectMapper mapper = JsonMapper.builder()
.polymorphicTypeValidator(ptv)
.build(); // with 2.10 and later (including 3.0)
// OR
mapper.setPolymorphicTypeValidator(ptv); // with 2.x only

Note that although you can use same validator instance, you will need to configure these separately.

Further considerations

Although BasicPolymorphicTypeValidator should be sufficient for most common cases, it may be necessary to build more complicated validators for more advanced usage. It is possible that new styles of rules could be added in BasicPolymorphicTypeValidator itself (for example, maybe set of “well-known” value types such as JDK “untyped” values; or ability to chain validators).

But you can also quite easily implement your own validators with custom logic. For example:

public class MyValidator extends PolymorphicTypeValidator.Base
{
public Validity validateSubClassName((MapperConfig<?> config, JavaType baseType, String subClassName) {
return subClassName.startsWith("com.foobar.") ?
Validity.ALLOWED : Validity.INDETERMINATE;
}
}

would allow all sub-classes in package com.foobar (and its sub-packages).

--

--

@cowtowncoder
@cowtowncoder

Written by @cowtowncoder

Open Source developer, most known for Jackson data processor (nee “JSON library”), author of many, many other OSS libraries for Java, from ClassMate to Woodstox

Responses (1)