15 Jul 2020 | Peter Stöckli
Fastjson: exceptional deserialization vulnerabilities
Many of you may never have heard of the Java based JSON serialization library called Fastjson, although it’s quite an interesting piece of software. Fastjson is an open source project of the Chinese Internet giant Alibaba and has 22’000 stars on GitHub (and coincidentally 1337 open issues) at the time writing of this blog post.
Like Jackson(-Databind) and other JSON serialization libraries Fastjson comes with a so-called AutoType-feature,
which instructs the library to deserialize JSON input using types provided by the JSON (using an extra JSON field called
Now we know at least since Muñoz/Mirosh’s Friday-The-13th-JSON-Attacks and Bechler’s marshalsec, that deserializing any input where the types can be provided is potentially insecure and dangerous.
And that is especially true if the types can be provided from a remote user (like a JSON object or a ViewState).
Now since the Fastjson developers are aware of this, autoType isn’t enabled by default. And even if it is enabled by the developer using this library there is an ever-growing
list of types that are not allowed at play.
Typical Fastjson RCEs (using the autoType-feature)
Needless to say that new classes that can cause some kind of RCE are discovered all the time, which then leads to the extension of the deny list and the release of a new version of Fastjson (a similar as path Jackson-databind had taken, before they replaced their deny list with an allow list).
An example of such a gadget would be the JDK class
javax.swing.JEditorPane, that worked until Fastjson 1.2.68 (released in March of 2020).
It can be used for remote detection of older Fastjson versions with autoType enbabled or alternatively to exploit a blind SSRF vulnerability.
A simple payload using that gadget would look like this:
If Fastjson before the version 1.2.69 with autoType enabled is in use and the payload above is parsed it instantiates the JDK class
javax.swing.JEditorPane and calls its
setPage method, which in turn makes a simple HTTP
GET request to the URL specified. (As said before this gadget is mostly interesting for the remote detection of a vulnerable application using Fastjson.)
Now it gets interesting…
Most people that know of the dangers of deserialization vulnerabilities won’t be surprised so far. However, while looking at the library I found some interesting things:
The global Fastjson instance
One of these interesting things is that there is a global Fastjson instance, that allows to change its serialization settings.
So, it might happen that one developer enables the
autoType feature on the global instance for storing some serialized values into a Redis datastore (which in itself is not that dangerous yet):
Whilst another developer parses JSON from a remote data source in another part of the same codebase, with the same global instance. So, a new RCE was created:
It looks so harmless, doesn’t it?
How many autoType checks?
In Fastjson 1.2.70 the
JSONException with the message
"autoType is not support."(sic) is thrown at nine different places in the
ParserConfig class. One could argue about the reasons why the authors deem this necessary. In my simplified, naïve view of the world I would expect one place of code where such an exception is thrown (“No really, autoType is not supported, we won’t instantiate your stupid class.). End of discussion.
But in this library, there are nine of those autoType checks, enabling people to find all kinds of unintended bypasses.
But can you make an Exception (instance), please?
So, let’s assume we have autoType disabled. Nine different checks should be enough? Right?
In Mai of 2020 someone discovered that despite autoType being disabled it was possible to instance Exceptions… and leak some data using them. Let’s look at how that was possible:
Just because autoType was not enabled didn’t mean that no classes could be instantiated. It just meant you couldn’t instantiate most classes…
So, with a simplified payload like:
it was possible to instantiate a simple RuntimeException, despite autoType being disabled.
When we look at what happens after we call
parse on the
com.alibaba.fastjson.JSON class we see the following behavior:
At one time both our types
java.lang.RuntimeException have to go through the
checkAutoType in the
ParserConfig class, where the over 200(!) lines long
checkAutoType method checks following things (excerpt):
safeModeis enabled (it is not)
- whether the type name is shorter or equal to 192 chars and at least 3 characters long
- whether the fnv1a_64-hash of our type name is in the
INTERNAL_WHITELIST_HASHCODESarray (it is not)
- whether the fnv1a_64-hash of our type name is in the
internalDenyHashCodesarray (it is not)
- Depending on which configuration flags were enabled it would also check against
Remember: These steps above happen, despite not having
Going forward the
createException method of the
ThrowableDeserializer class tries to instantiate the exception using three different constructors in this order:
At the end with have an instantiated exception on that we can call getters and setters.
The Selenium gadget
Another exception gadget payload, that can be found in the learnjavabug GitHub repository of threedr3am is using the
WebDriverException of Selenium.
This payload could be used to leak some system information. But not that easily: First of all, it needs a web application that somehow reflects the input data and secondly it requires Selenium
on the classpath, which should rarely be the case. (Selenium is mostly used for integration tests and should only be on the classpath that is used for testing.)
A simple version of that payload looks like this (note the $ref):
If the web application reflects the value of the
content property somewhere, system information such as the following could be “leaked”:
Not that interesting information to be honest, but might be interesting if you want to detect if older versions of Fastjson are in use (requires the vulnerable web application to have Selenium on their classpath).
Searching for more gadgets using CodeQL and LGTM
Instead of searching for vulnerabilities using GitHub Security Lab’s amazing CodeQL, I thought it would be interesting to leverage CodeQL on LGTM as a tool to find additional Exception gadgets using a CodeQL query similar to this:
I ran an extended version of this query against some popular Java libraries, but did not find any interesting gadgets (due to the nature of these gadgets this was expected). However, CodeQL in combination with LGTM is definitively a comfortable way of searching for gadget candidates.
Detection with a simple gadget
It turns out that for detection it might be enough to misuse the
getStackTrace method implemented on
Throwable. In that case any Exception that somehow inherits from
Throwable would do (e.g. java.lang.RuntimeException):
This detection method also requires that an attribute (like
content) is reflected somewhere.
The safe mode
With version 1.2.68 the Fastjson developers introduced the so-called safe mode. One way to turn on safe mode is to call
true on the global config instance:
The check for the safe mode flag takes place almost at the top of the already mentioned
checkAutoType method in the
ParserConfig class and throws an exception when Fastjson wants to instantiate an arbitrary type.
However, safe mode is not enabled by default…
Basically Fastjson looks like a gift to the information security world that will keep on giving…
I didn’t even have the time to look into other interesting features of Fastjson like reference or JSONPath/Regex support, so there’s probably much more interesting stuff in the hiding. Now while Fastjson might seem like an extremely powerful library, it’s probably too powerful for its own good. (Or at least for the developers using it.)
Should you use this library for handling user input? Probably not. Consider using JSON libraries with less features, that prevent you from shooting into your own foot. Like Gson.