Java安全-Fastjson基础

Java安全-Fastjson基础

简介

Fastjson 是 Alibaba 开发的 Java 语言编写的高性能 JSON 库,用于将数据在 JSON 和 Java Object 之间互相转换。

提供两个主要接口来分别实现序列化和反序列化操作。

JSON.toJSONString 将 Java 对象转换为 json 对象,序列化的过程。

JSON.parseObject/JSON.parse 将 json 对象重新变回 Java 对象;反序列化的过程

  • 所以可以简单的把 json 理解成是一个字符串。

  • Fastjson 是 Alibaba 开发的 Java 语言编写的高性能 JSON 库,用于将数据在 JSON 和 Java Object 之间互相转换。

    提供两个主要接口来分别实现序列化和反序列化操作。

    JSON.toJSONString 将 Java 对象转换为 json 对象,序列化的过程。

    JSON.parseObject/JSON.parse 将 json 对象重新变回 Java 对象;反序列化的过程

    • 所以可以简单的把 json 理解成是一个字符串。

代码demo

序列化代码实现

这里通过 Demo 了解下如何使用 Fastjson 进行序列化和反序列化,以及其中的一些特性之间的区别等等。

首先,pom.xml 里面导入 Fastjson 的依赖,这里先导入 1.2.24 的

定义一个类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.sf.maven.fastjsondemo;

public class User {
private String name;
private int age;

public User() {
System.out.println("构造函数");
}

public int getAge() {
return age;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
System.out.println(this.name);
}

public void setAge(int age) {
this.age = age;
System.out.println(this.age);
}
}

然后写序列化的代码,调用 JSON.toJsonString() 来序列化 User 类对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.sf.maven.fastjsondemo;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;

public class Fastjson {
public static void main(String[] args) {
User user = new User();
user.setAge(18);
user.setName("aa");
System.out.println(user);

// 转换数据格式为json
// 将json对象转化成json数据
String s=JSONObject.toJSONString(user);
System.out.println("json格式:"+s);

// 分析漏洞利用 多输出 转换数据的类型 前面自带type-> 转换对象类包
String s1=JSONObject.toJSONString(user, SerializerFeature.WriteClassName);
System.out.println(s1);
}
}

漏洞初步分析

这里对比一下转化成json数据的两种写法

SerializerFeature.WriteClassName 会在序列化 JSON 时把 Java 对象的类型信息写入 JSON(默认字段名是 @type)。也就是说,序列化结果里会多一个表示类名的字段,用于在反序列化时还原成原始的具体类

1
2
3
4
5
6
7
8
9
10
11
User user = new User();
user.setAge(18);
user.setName("aa");

// 不写 WriteClassName
String s = JSONObject.toJSONString(user);
// 输出: {"age":18,"name":"aa"}

// 写了 WriteClassName
String s1 = JSONObject.toJSONString(user, SerializerFeature.WriteClassName);
// 输出: {"@type":"com.sf.maven.fastjsondemo.User","age":18,"name":"aa"}

可以看出,多了 @type 字段并且值为类的全限定名

为什么要这样写(什么时候需要)?

  • 需要在反序列化时恢复具体类型:当你把 Object、或父类/接口引用序列化(例如 List<Animal> 里可能有 Dog/Cat),再反序列化回 Java 时,如果没有类型信息,Fastjson 无法知道每个元素的具体子类。WriteClassName 可以把这个信息写进去,方便恢复原始类型。
  • RPC / 分布式传输场景:发送端希望接收端能准确还原对象类型时,会把类型信息带上。

安全风险(非常重要,fastjson反序列化的关键点)

@type 写入并在反序列化时启用自动类型解析(autoType)会带来严重安全风险:如果你接收并直接 JSON.parse()(或使用不安全的反序列化方式),恶意构造的 @type 可以触发反序列化利用链(gadget),导致任意代码执行。
因此:

  • 不要对来自不可信来源的 JSON 启用自动类型解析。
  • 尽量不要在开放接口上使用 WriteClassName + 无限制的反序列化。
  • 更安全的做法是 显式指定目标类(见下)

调试查看序列化逻辑

这个地方,序列化的逻辑我们可以稍微调试看一下

首先会进到 JSON 这个类,然后进到它的 toJSONString() 的函数里面,new 了一个 SerializeWriter 对象。我们的序列化这一步在这里就已经是完成了。

在进到 JSON 这个类里面的时候多出了个 static 的变量,写着 “members of JSON”,这里要特别注意一个值 DEFAULT_TYPE_KEY 为 “@type”,这个挺重要的

之后就是一个赋值过程

里面定义了一些初值,赋值给 out 变量,这个 out 变量后续作为 JSONSerializer 类构造的参数。

之后走到wirte方法,写入我们新建对象的值

继续往下面走,就是显示的部分了,toString() 方法

最后的运行结果

着重来分析一下这行代码

1
String s1=JSONObject.toJSONString(user, SerializerFeature.WriteClassName);

第二个参数是 SerializerFeature.WriteClassName,是 JSON.toJSONString() 中的一个设置属性值,设置之后在序列化的时候会多写入一个@type,即写上被序列化的类名,type 可以指定反序列化的类,并且调用其 getter/setter/is 方法。

  • Fastjson 接受的 JSON 可以通过@type字段来指定该JSON应当还原成何种类型的对象,在反序列化的时候方便操作。

输出如下:

1
2
3
4
5
6
7
8
9
10
11
User user = new User();
user.setAge(18);
user.setName("aa");

// 不写 WriteClassName
String s = JSONObject.toJSONString(user);
// 输出: {"age":18,"name":"aa"}

// 写了 WriteClassName
String s1 = JSONObject.toJSONString(user, SerializerFeature.WriteClassName);
// 输出: {"@type":"com.sf.maven.fastjsondemo.User","age":18,"name":"aa"}

反序列化代码实现

调用 JSON.parseObject(),代码如下

根据我们之前分析的,这里的反序列化操作是极有可能存在安全漏洞的

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.sf.maven.fastjsondemo;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;

public class Unserialize01 {
public static void main(String[] args) {
String jsonString = "{/"@type/":/"com.sf.maven.fastjsondemo.User/",/"age/":18,/"name/":/"aa/"}";
User a = JSON.parseObject(jsonString, User.class, Feature.SupportNonPublicField);
System.out.println(a);
System.out.println(a.getClass().getName());
}
}

看一下运行结果

就这样代码demo基本可以结束了

其他的一些知识

反序列化时的 Feature.SupportNonPublicField 参数

Feature.SupportNonPublicFieldfastjson 中的一个序列化/反序列化特性,它控制在反序列化过程中是否支持访问类的非公共(privateprotected)字段。

  1. 默认情况下

    fastjson 中,反序列化时,默认只会访问类的公共字段(public)或者公共的 getter/setter 方法。如果字段是 privateprotected,而你没有显式为它们提供访问方法(如 getter 或 setter),那么它们不会被自动反序列化。

  2. 启用 Feature.SupportNonPublicField

    如果你在 JSON.parseObjectJSON.parse 方法中启用了 Feature.SupportNonPublicFieldfastjson 会尝试通过反射访问并填充所有字段(包括 privateprotected 字段),即使这些字段没有 getter/setter 方法。

示例

Person

1
2
3
4
5
6
7
8
9
10
public class Person {
private String name;
private int age;
// 构造函数
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 没有getter/setter方法
}

如果我们想把一个包含 nameage 字段的 JSON 字符串反序列化为 Person 对象:

1
2
3
4
5
6
7
8
9
String json = "{/"name/":/"John/", /"age/":30}";

// 不启用 SupportNonPublicField(默认)
Person person = JSON.parseObject(json, Person.class);
// 结果: person 对象的 name 和 age 字段不会被填充,因为它们是 private,没有 getter/setter。

// 启用 SupportNonPublicField
Person personWithSupport = JSON.parseObject(json, Person.class, Feature.SupportNonPublicField);
// 结果: personWithSupport 对象的 name 和 age 字段会被填充,即使它们是 private。

parse与parseObject区别

前面的demo都是用parseObject()演示的,还没说到parse()。两者主要的区别就是parseObject()返回的是JSONObject而parse()返回的是实际类型的对象,当在没有对应类的定义的情况下,一般情况下都会使用JSON.parseObject()来获取数据。

1
FastJson中的 `parse()` 和 `parseObject()` 方法都可以用来将JSON字符串反序列化成Java对象,`parseObject()` 本质上也是调用 `parse()` 进行反序列化的。但是 `parseObject()` 会额外的将Java对象转为 JSONObject对象,即 `JSON.toJSON()`。所以进行反序列化时的细节区别在于,`parse()` 会识别并调用目标类的 `setter` 方法及某些特定条件的 `getter` 方法,而 `parseObject()` 由于多执行了 `JSON.toJSON(obj)`,所以在处理过程中会调用反序列化目标类的所有 `setter` 和 `getter` 方法。

也就是说,我们用parse()反序列化会直接得到特定的类,而无需像parseObject()一样返回的是JSONObject类型的对象、还可能需要去设置第二个参数指定返回特定的类。

parseObject():返回fastjsonJSONObject

parse():返回我们的类

下面我们可以看到,parseObject()返回parseObject类,而parse返回我们的User类

同时可以看到,这里parseObject的参数不带指定类型是无法反序列化成功的

必须加上指定反序列化得到的类型为Object.class或User.class,这样就是成功反序列化的回显

总结一下

我们的反序列化的语句应该这么写

1
2
3
String jsonString ="{/"@type/":/"Student/",/"age/":6," +  
"/"name/":/"Drunkbaby/",/"address/":/"china/",/"properties/":{}}";
Object obj = JSON.parseObject(jsonString, Student.class);

要用 parseObject,里面的参数需要是 Object.class

fastjson 反序列化漏洞原理

fastjson 在反序列化的时候会去找我们在 @type 中规定的类是哪个类,然后在反序列化的时候会自动调用这些 setter 与 getter 方法的调用,注意!并不是所有的 setter 和 getter 方法。

下面直接引用结论,Fastjson会对满足下列要求的setter/getter方法进行调用:

满足条件的setter:

  • 非静态函数
  • 返回类型为void或当前类
  • 参数个数为1个

满足条件的getter:

  • 非静态方法
  • 无参数
  • 返回值类型继承自Collection或Map或AtomicBoolean或AtomicInteger或AtomicLong

fastjson反序列化漏洞利用只需要更改传入的字符串即可

漏洞原理

由前面知道,Fastjson是自己实现的一套序列化和反序列化机制,不是用的Java原生的序列化和反序列化机制。无论是哪个版本,Fastjson反序列化漏洞的原理都是一样的,只不过不同版本是针对不同的黑名单或者利用不同利用链来进行绕过利用而已。

通过Fastjson反序列化漏洞,攻击者可以传入一个恶意构造的JSON内容,程序对其进行反序列化后得到恶意类并执行了恶意类中的恶意函数,进而导致代码执行。

那么如何才能够反序列化出恶意类呢?

由前面demo知道,Fastjson使用parseObject()/parse()进行反序列化的时候可以指定类型。如果指定的类型太大,包含太多子类,就有利用空间了。例如,如果指定类型为Object或JSONObject,则可以反序列化出来任意类。例如代码写Object o = JSON.parseObject(poc,Object.class)就可以反序列化出Object类或其任意子类,而Object又是任意类的父类,所以就可以反序列化出所有类。

接着,如何才能触发反序列化得到的恶意类中的恶意函数呢?

由前面知道,在某些情况下进行反序列化时会将反序列化得到的类的构造函数、getter方法、setter方法执行一遍,如果这三种方法中存在危险操作,则可能导致反序列化漏洞的存在。换句话说,就是攻击者传入要进行反序列化的类中的构造函数、getter方法、setter方法中要存在漏洞才能触发。

小结一下

若反序列化指定类型的类如Student obj = JSON.parseObject(text, Student.class);,该类本身的构造函数、setter方法、getter方法存在危险操作,则存在Fastjson反序列化漏洞;

若反序列化未指定类型的类如Object obj = JSON.parseObject(text, Object.class);,该若该类的子类的构造方法、setter方法、getter方法存在危险操作,则存在Fastjson反序列化漏洞;

POC

一般的,Fastjson反序列化漏洞的PoC写法如下,@type指定了反序列化得到的类

1
2
3
4
5
{
"@type":"xxx.xxx.xxx",
"xxx":"xxx",
...
}

关键是要找出一个特殊的在目标环境中已存在的类,满足如下两个条件:

  1. 该类的构造函数、setter方法、getter方法中的某一个存在危险操作,比如造成命令执行;
  2. 可以控制该漏洞函数的变量(一般就是该类的属性);

漏洞demo

这个demo非常简单

Student.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import java.util.Properties;  

public class Student {
private String name;
private int age;
private String address;
private Properties properties;

public Student() {
System.out.println("构造函数");
}

public String getName() {
System.out.println("getName");
return name;
}

public void setName(String name) {
System.out.println("setName");
this.name = name;
}

public int getAge() {
System.out.println("getAge");
return age;
}

// public void setAge(int age) {
// System.out.println("setAge");
// this.age = age;
// }

public String getAddress() {
System.out.println("getAddress");
return address;
}

public Properties getProperties() throws Exception{
System.out.println("getProperties");
Runtime.getRuntime().exec("calc");
return properties;
}
}

FastjsonEasyPoC.java

1
2
3
4
5
6
7
8
9
10
11
12
13
import com.alibaba.fastjson.JSON;  
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;

public class FastjsonEasyPoC {
public static void main(String[] args){
String jsonString ="{/"@type/":/"Student/",/"age/":6,/"name/":/"aa/",/"address/":/"china/",/"properties/":{}}";

Object obj = JSON.parseObject(jsonString, Object.class);
System.out.println(obj);
System.out.println(obj.getClass().getName());
}
}

运行后能够顺利弹出计算器


Java安全-Fastjson基础
http://huang-d1.github.io/2025/09/28/Java安全-Fastjson基础/
作者
huangdi
发布于
2025年9月28日
许可协议