JCK00-G: Prevent Jackson Remote Code Execution
Introduction
Jackson is a JavaScript Object Notation (JSON) processor for Java that allows you to convert Java objects to JSON and back. It uses the com.fasterxml.jackson.databind.ObjectMapper
class to handle this serialization and deserialization process. Jackson's automatic detection of properties simplifies the task by figuring out how to serialize and deserialize fields. Public fields are the easiest to handle, but fields with getter methods are also serializable and deserializable.
Be cautious when deserializing JSON from untrusted sources, as it can leave your code vulnerable to serious attacks such as remote command execution (RCE) and denial-of-service (DoS). These vulnerabilities are a result of Jackson's deserialization being able to handle polymorphic types and overly general superclasses, which attackers can exploit. Many of the vulnerabilities with the Jackson library are related to default typing, which is not enabled by default. Default typing is a Jackson ObjectMapper mechanism designed to handle polymorphic types and inheritance. It allows you to deserialize JSON to a Java POJO even if you are unsure of the subtype of the object or field, by simply deserializing to the superclass. Enabling default typing globally can take inheritance to the extreme, potentially leading to security risks.
Jackson automatically treats fields with getter methods as properties, making them serializable and deserializable even if they are not public. This means non-public fields can be accessed and manipulated if matching getter and setter methods exist. Non-private getters can access non-public fields, and setter methods mark non-public fields as deserializable. Consequently, fields not intended to be exposed can be unintentionally manipulated, allowing attackers to inject malicious data or execute harmful commands through carefully crafted JSON. This automatic handling increases the risk of vulnerabilities when deserializing JSON from untrusted sources.
When using Jackson, pay attention to the visibility of getters and setters and refrain from deserializing JSON to overly generic types. Ensure that the correct type information is included in JSON representations for handling Java inheritance and non-concrete types, such as abstract classes and interfaces
Non-Compliant Code Example
In the following non-compliant code example, the deserialize
function takes a JSON string as the single argument. It creates a new ObjectMapper
(which can be an expensive operation that is typically only performed once). The function then calls mapper.enableDefaultTyping
to enable polymorphic type handling. Deserialization must support polymorphic type handling to enable Java inheritance and non-concrete types, such as abstract classes and interfaces. The deserialization of these objects requires appropriate type information be included in the object's representation to be restored as the appropriate types. Invoking mapper.enableDefaultTyping
is equivalent to adding:
@JsonTypeInfo(use = Id.CLASS, include = As.WRAPPER_ARRAY)
on every property that matches inclusion criteria (DefaultTyping.OBJECT_AND_NON_CONCRETE
, by default).
Finally, the function calls mapper.readValue
to deserialize JSON content from the data parameter.
// Noncompliant Code Example
private static function deserialize(data : String) : Object {
var mapper = new ObjectMapper()
mapper.enableDefaultTyping()
return mapper.readValue(data, Object)
}
This code is vulnerable to remote code execution (RCE), denial-of-service (DoS), and other attacks. These attacks are possible when an attacker can control the JSON contents to specify the deserialization of dangerous objects that will invoke specific methods already present in the JVM with attacker-supplied data.
Security researchers have identified a wide variety of existing classes that can be used to violate the security policies of a vulnerable Java program. These classes are often referred to as gadgets because they are similar to gadgets in return-oriented programming. Gadgets consist of existing, executable code present in the vulnerable process that an attacker can maliciously repurpose. For example, in Jackson deserialization vulnerabilities, these classes contain code that is executed when an object is deserialized.
The deserialize
function is vulnerable because it supports polymorphic type handling for properties with a nominal type of java.lang.Object
(or permissive tag interfaces such as java.io.Serializable
). This combination allows an attacker to provide a JSON string for a gadget to execute attacker-specified code within your JVM. For more information on these exploits, see Jackson Deserialization Vulnerabilities.
Compliant Solution
Ensure you are using updated versions of Jackson (jackson-databind
): upgrading to the latest patch version of the given minor version should always be safe, as there should be no breaking changes to functionality.
If possible, avoid enabling default typing (because it is usually class name-based). Instead, it is better to be explicit about specifying where polymorphism is needed.
Avoid using unsafe base types as the nominal type of polymorphic values, regardless of whether you use per-type, per-property, or Default Typing. Unsafe base types include:
java.lang.Object
java.io.Closeable
java.io.Serializable
java.lang.AutoCloseable
java.lang.Cloneable
java.util.Comparable
Also, watch for these less common but potentially unsafe base types:
java.util.logging.Handler
javax.naming.Referenceable
javax.sql.DataSource
If possible, use “type name” and not classname for the type id: @JsonTypeInfo(use = Id.NAME
); this may require annotating the type name (see @JsonTypeName and @JsonSubTypes
).
The following compliant solution is similar to the previous non-compliant code example. Still, it does not attempt to encapsulate the functionality in a function call, as the code is specific to the JSON being deserialized. This code defines a JSON string called good, which should be deserializable by this code, and a second JSON string called bad should fail during deserialization to prevent exploitation.
// Compliant Solution
uses jck00.*
private static final var command : String = System.getProperty("os.name")
.toLowerCase()
.contains("windows") ? "calc.exe" : "gedit"
private static final var good : String =
"{\"name\":\"John Doe\",\"age\":101,\"phone\":{" +
"\"@class\":\"jck00.DomesticNumber\", \"areaCode\":0, \"local\":0}}"
private static final var bad : String =
"{\"name\":\"Bender\",\"age\":101,\"phone\":{" +
"\"@class\":\"com.popular.lib.Exec\", \"command\":\"" +
command + "\"}}"
var ptv : PolymorphicTypeValidator = BasicPolymorphicTypeValidator
.builder()
.allowIfSubType("jck00")
.build()
var mapper : ObjectMapper = JsonMapper
.builder()
.polymorphicTypeValidator(ptv)
.build()
var person : Person = mapper.readValue(good, Person)
Jackson 2.10 allows the implementation of security checks that are specific to a particular application. The following new APIs are used in this solution to implement such security checks:
- Abstract
PolymorphicTypeValidator
class can be extended to implement validating classes during deserialization. BasicPolymorphicTypeValidator
class extendsPolymorphicTypeValidator
and implements allow lists and deny lists for validating classes.- New methods such as
activateDefaultTyping(PolymorphicTypeValidator, …)
andpolymorphicTypeValidator(...)
allow specifying validators for classes during deserialization.
In this compliant solution, the basic polymorphic type validator ptv
only allows classes in package jck00
to be deserialized, preventing the deserialization of unauthorized gadgets.
Risk Assessment
Exceptions may inadvertently reveal sensitive information unless care is taken to limit the information disclosure.
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
JCK00-G | High | Likely | Low | L9 | L1 |
Additional Resources
Was this page helpful?