How Fastjson’s AutoType Bypass Enables File Read and SSRF Attacks
This article provides a detailed analysis of the recent Fastjson deserialization vulnerability, explaining how the autoType bypass can be exploited to achieve arbitrary file reads, SSRF, and other attacks by leveraging gadget classes such as AutoCloseable, and walks through the debugging process and code paths involved.
Preface
The analysis of the delayed Fastjson deserialization vulnerability is presented without a public PoC, noting that exploitation methods are diverse. Beyond previously disclosed file‑write techniques, the vulnerability can also be leveraged for SSRF attacks.
1. Vulnerability Overview
Earlier articles showed exploitation by clearing a target file and writing arbitrary content using third‑party libraries. When the gadget class inherits from the first class in the hierarchy, Fastjson’s auto‑type check can be bypassed, allowing the attacker to supply a class that satisfies the expected type check.
This article examines a method that directs deserialization to Fastjson’s built‑in classes, enabling file read, SSRF, and other malicious actions.
2. Debug Analysis
2.1 Vulnerability Debugging
The updated patch adds three methods to expectClass: java.lang.Runnable, java.lang.Readable, and java.lang.AutoCloseable. The parseObject method parses input, extracts the type name, and invokes checkAutoType when the value is not numeric.
if (!allDigits) {
clazz = config.checkAutoType(typeName, null, lexer.getFeatures());
}If the supplied data is non‑numeric, expectClass defaults to null and checkAutoType validates the class.
final boolean expectClassFlag;
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 = false;
} else {
expectClassFlag = true;
}
}The flag remains false because autoCloseable is not on the whitelist or blacklist, and autoTypeSupport is disabled.
The checking process involves three steps:
Hash‑based internal whitelist matching.
Hash‑based internal blacklist matching.
If not in the internal whitelist and autoTypeSupport is enabled (or the expected class is supplied), hash verification against acceptHashCodes and denyHashCodes determines whether the class is loaded or rejected.
clazz = TypeUtils.getClassFromMapping(typeName);After passing step C, clazz is assigned and further examined.
if (clazz != null) {
if (expectClass != null && clazz != java.util.HashMap.class &&
!expectClass.isAssignableFrom(clazz)) {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}
return clazz;
}The deserializer for the resolved class is obtained and the object is instantiated via JavaBeanDeserializer, which extends MapDeserializer:
ObjectDeserializer deserializer = config.getDeserializer(clazz);
Class deserClass = deserializer.getClass();
if (JavaBeanDeserializer.class.isAssignableFrom(deserClass) &&
deserClass != JavaBeanDeserializer.class &&
deserClass != ThrowableDeserializer.class) {
this.setResolveStatus(NONE);
} else if (deserializer instanceof MapDeserializer) {
this.setResolveStatus(NONE);
}
Object obj = deserializer.deserialze(this, clazz, fieldName);
return obj;The deserialization proceeds with the gadget class (e.g., AutoCloseable) and eventually loads the class file:
InputStream is = null;
try {
String resource = typeName.replace('.', '/') + ".class";
if (defaultClassLoader != null) {
is = defaultClassLoader.getResourceAsStream(resource);
} else {
is = ParserConfig.class.getClassLoader().getResourceAsStream(resource);
}
if (is != null) {
ClassReader classReader = new ClassReader(is, true);
TypeCollector visitor = new TypeCollector("<clinit>", new Class[0]);
classReader.accept(visitor);
jsonType = visitor.hasJsonType();
}
} catch (Exception e) {
// skip
} finally {
IOUtils.close(is);
}This step reads the gadget’s bytecode, enabling further exploitation such as arbitrary file reads.
if (autoTypeSupport || jsonType || expectClassFlag) {
boolean cacheClass = autoTypeSupport || jsonType;
clazz = TypeUtils.loadClass(typeName, defaultClassLoader, cacheClass);
} if (expectClass != null) {
if (expectClass.isAssignableFrom(clazz)) {
TypeUtils.addMapping(typeName, clazz);
return clazz;
} else {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}
}The isAssignableFrom check confirms that the loaded class inherits from AutoCloseable, which is the key to the exploit chain.
After the class is added to the internal mapping, Fastjson proceeds with deserialization of the gadget, completing the attack.
2.2 Summary
The critical insight of this vulnerability lies in identifying a commonly used JAR gadget that can be deserialized to trigger malicious behavior, including RCE, arbitrary file read/write, and SSRF. Properly controlling the auto‑type mechanism and whitelist/blacklist configurations is essential to mitigate such attacks.
References
https://b1ue.cn/archives/348.html
https://daybr4ak.github.io/2020/07/20/fastjson%201.6.68%20autotype%20bypass/
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
