Java JSON deserialization problems with the Jackson ObjectMapper
Tags: java, json, security
Published: 2022-01-04 15:50:15

In a previous blog post, we took a look at [Java's custom serialization](https://snyk.io/blog/serialization-and-deserialization-in- java/) platform and what the security implications are. And more recently, I wrote about [how improvements in Java 17](https://snyk.io/blog/new- java-17-features-for-improved-security-and-serialization/) can help you prevent insecure deserialization. However, nowadays, people aren't as dependent on Java's custom serialization, opting instead to use JSON. JSON is the most widespread format for data serialization, it is human readable and not specific to Java.

One of the most commonly used libraries is `jackson-databind`, which provides you with an `ObjectMapper` to transform your object into JSON and vice versa.

As it is a popular library, it's very important to know that the `jackson- databind` library has been subject to many [reported vulnerabilities](https://snyk.io/learn/owasp-top-10-vulnerabilities/) over the past couple of years. Nevertheless, this does not mean that using the Jackson ObjectMapper is a [security risk](https://snyk.io/learn/security- vulnerability-exploits-threats/) by default. This article will explain how the Jackson deserialization vulnerabilities work and how to make sure you are not affected by them.

## Using the Jackson ObjectMapper to create Java objects from JSON

With the code below, we can create an `ObjectMapper` and use it to recreate a `Person` from a JSON string that comes from a file.

```java
ObjectMapper om = new ObjectMapper(); 
Person myvalue = om.readValue(Files.readAllBytes(Paths.get("person.json")), Person.class); 
System.out.println("name:"+myvalue.name+" \n"+"Age:"+myvalue.age);
```

This is all straightforward, and nothing really fancy is going to happen. Without any annotations, the Jackson ObjectMapper uses reflection to do the POJO mapping. Because of the reflection, it works on all fields regardless of the access modifier. If there are getters and setters available, the Jackson ObjectMapper will use that to do the mapping.

### Default typing in Jackson

Many of the vulnerabilities with the Jackson library for JSON serialization depend on default typing, which is **not enabled by default** . You need to enable this explicitly. This means in most cases the vulnerabilities named in this list do not affect your system directly.

But let's explain what default typing is and what it is used for.

Default typing is a mechanism in the Jackson ObjectMapper to deal with polymorphic types and inheritance. If you want to deserialize JSON to a Java POJO but are unsure what subtype the object or the field is, you can simply deserialize to the superclass.

Say you have `Coffee` and `Tea`. Both classes have the same superclass of `HotDrink`. So if your `Breakfast` contains a `HotDrink` but you are unaware if it is either `Coffee` or `Tea` you can use default typing to solve this.

```java
public class Breakfast {
   public String food;
   public HotDrink drink;
}

public abstract class HotDrink {
   public String name;
}

public class Coffee extends HotDrink {
   @Override
   public String toString() {
       return String.format("Coffee{name='%s'}", name);
   }
}


public class Tea extends HotDrink {
   @Override
   public String toString() {
       return String.format("Tea{name='%s'}", name);
   }
}

String breakfastJson = """
       {
           "food":"sandwich",
           "drink":["nl.brianvermeer.example.jackson.serialization.Tea",{"name":"oolong"}]
       }
       """;

var om = new ObjectMapper();
om.enableDefaultTyping();

var myBreakfast = om.readValue(breakfastJson, Breakfast.class);
System.out.println("breakfast hotdrink:"+myBreakfast.drink);
```


In this example, I enabled default typing on the ObjectMapper so that I can handle polymorphism everywhere I use this `ObjectMapper`. You can also do this on a specific field using the `@JsonTypeInfo` annotation.

## Security problems with default typing on the Jackson ObjectMapper

So if default typing is enabled globally, it is possible to take inheritance to the extreme. If your `Breakfast` does not contain a `HotDrink` but a field of type `Object`, then any object can be available on the classpath. This also means we can deserialize any object that is available on the classpath. Potentially, this can be a gadget object that sets up a gadget chain and eventually ends in a remote execution.

These gadget chains are pretty similar to those described in my [Serialization and deserialization in Java](https://snyk.io/blog/serialization-and- deserialization-in-java/) blog post. Let's simplify this with a single gadget that executes a command right away when initialized. My `SecondBreakfast` class blow, containing a drink of type `Object`.

```java
public class Gadget {

   private Runnable command;

   public Gadget(String value) {
       this.command = new Command(value);
       this.command.run();
   }
}

public class SecondBreakfast {
   public String food;
   public Object drink;
}
```


If I deserialize my `SecondBreakfast` with default typing enabled, I can deserialize an `Object` containing an arbitrary code execution.

```java
String secondBreakfastJson = """
       {
           "food":"sandwich",
           "drink":["nl.brianvermeer.example.jackson.serialization.Gadget", "rm -rf *"]
       }
       """;

var om = new ObjectMapper();
om.enableDefaultTyping();



Var mySecondBreakfast = om.readValue(secondBreakfastJson, SecondBreakfast.class);
System.out.println("Second breakfast hotdrink:"+mySecondBreakfast.drink);
```


Now this is a simplified example. But while finding and creating a gadget chain is not easy, it definitely is possible. Because of all the libraries and frameworks we use, chances are that a combination of all the classes in your classpath can be used to create such a gadget chain.

Also, there is already a large set of well-known "nasty classes" identified. Deserialization of such a class is considered dangerous and basically follows the pattern as described above. For reference, check the [list of deserialization vulnerabilities](https://security.snyk.io/search?type=maven&q=jackson- databind) on the `jackson-databind` library on the Snyk vulnerability database

## How does this impact my application?

Ok, don't panic because it is not that bad. First of all, the maintainers of the `jackson-databind` library actively block the set of "nasty classes" in the [SubTypeValidator](https://github.com/FasterXML/jackson- databind/blob/2.14/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/SubTypeValidator.java). Secondly, you need to enable default types explicitly. This means by default, this setting is off, and polymorphic deserialization is not even possible.

Scanning with [SCA tools](https://snyk.io/blog/what-is-software-composition- analysis-sca-and-does-my-company-need-it/) like [Snyk](https://snyk.io/product/open-source-security-management/) does show the vulnerability in the scan result. A quick search shows that there were a lot of these problems in the past. It is always wise to update to the newest version because the library is well maintained, and new "nasty classes" will be actively blocked when found. Nevertheless, the best way is to prevent enabling default typing of your Jackson `ObjectMapper`.

### Snyk triage assistant to the rescue

Snyk is currently making an effort to filter these vulnerabilities if your code does not meet the prerequisites. For the Jackson ObjectMapper this means, if your code does not enable polymorphic typing, we will show you that it is unlikely that this specific vuln will exploit you. At the time of writing, this feature is only released for the Jackson deserialization vulnerability, but the team continues working on improving and expanding this feature. To use this feature, we need approval to scan your code to check if you do not have default typing enabled.

![](https://snyk.io/wp-content/uploads/blog-jackson-objectmapper-snyk-1240x481.png)

The code examples used in this blog post are published on my [GitHub](https://github.com/bmvermeer/jackson-serialization) account. Feel free to fork or reuse these examples in any way you prefer.

## Deserialize safely!

The best way to avoid deserialization problems with the Jackson ObjectMapper is to prevent polymorphic typing. Please do not enable default typing for your ObjectMapper. Also, connect your project to Snyk to find out if you are using a `jackson-databind` library with known vulnerabilities. In most cases, you can easily replace it with a newer version. When you enable code scanning, the Triage Assistant can help you determine if it is likely or unlikely to be exploitable.