Java-Fastjson基础
Flow

Fastjson简介

Fastjson 是一个 Java 库,可以将 Java 对象转换为 JSON 格式,当然它也可以将 JSON 字符串转换为 Java 对象。

Fastjson 可以操作任何 Java 对象,即使是一些预先存在的没有源码的对象。

Fastjson 源码地址:https://github.com/alibaba/fastjson

Fastjson 中文 Wiki:https://github.com/alibaba/fastjson/wiki/Quick-Start-CN

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

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

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

能够把json理解成字符串

Demo

简单看一下是怎么序列化反序列化的

先导入依赖

1
2
3
4
5
<dependency>  
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>

先写一个human类

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
public class human {
private String name;
private int age;

public human(){
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;
}
}

序列化

写一个humanSerialize

1
2
3
4
5
6
7
8
9
10
11
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;

public class humanSerialize {
public static void main(String[] args) {
human human = new human();
human.setName("Flow");
String jsonString = JSON.toJSONString(human, SerializerFeature.WriteClassName);
System.out.println(jsonString);
}
}

下个断点看这个过程

会先到JSON这个类,然后toJSONString()->new SerializeWriter(),序列化在这里完成的,里面定义了一些初值,赋值给 out 变量,这个 out 变量后续作为 JSONSerializer 类构造的参数

JSON里面有很多个static变量,其中就有一个DEFAULT_TYPE_KEY值为@type

然后后面就是toString了,看一下运行结果

1
2
3
4
5
构造函数
setName
getAge
getName
{"@type":"human","age":0,"name":"Flow"}

代码重点在

1
String jsonString = JSON.toJSONString(human, SerializerFeature.WriteClassName);

主要是第二个参数,SerializerFeature.WriteClassNameJSON.toJSONString() 里一个设置属性值,设置之后可以在后面序列化数据多加一个@type,也就是写上被序列化的类名,type可以指定反序列化的类,并调用getter/setter/is 方法。

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

没有设置SerializerFeature.WriteClassName是输出:

1
2
3
4
5
构造函数
setName
getAge
getName
{"age":0,"name":"Flow"}

反序列化

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

public class humanUnSerialize {
public static void main(String[] args) {
String jsonString = "{\"@type\":\"human\",\"age\":0,\"name\":\"Flow\"}";
human human = JSON.parseObject(jsonString, human.class, Feature.SupportNonPublicField);
System.out.println(human);
System.out.println(human.getClass().getName());
}
}

运行结果就这样

1
2
3
4
5
构造函数
setAge
setName
human@6659c656
human

大概就是这么用

一些基础知识

反序列化时的 Feature.SupportNonPublicField 参数

前面示例中反序列化的时候没办法获取到age的值,因为他是private的,如果要还原出private属性的话,需要在JSON.parseObject/JSON.parse中加上Feature.SupportNonPublicField参数

现在把human类里面的setAge函数注释掉,此时name是public,age是private,做一个对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
===没有Feature.SupportNonPublicField===
构造函数
setName
human@6659c656
getName
Flow
getAge
0
====有Feature.SupportNonPublicField====
构造函数
setName
human@6659c656
getName
Flow
getAge
20

所以总的来说,把json传到JSON.parseObject()进行反序列化,如果要内容里面的私有变量被成功地还原出来,要加上Feature.SupportNonPublicField参数

只进行 JSON.parseObject(jsonString)

看一下parseObject()的指定或不指定对反序列化类型之间的差异

修改human类,添加两个private成员变量,且所有的私有成员变量都不定义setter方法

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
import java.util.Properties;

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

public human() {
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 String getAddress() {
System.out.println("getAddress");
return address;
}

public Properties getProperties() {
System.out.println("getProperties");
return properties;
}
}

现在的反序列化代码

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


public class humanUnSerialize {
public static void main(String[] args) {
String jsonString ="{\"@type\":\"human\",\"age\":20," +
"\"name\":\"Flow\",\"address\":\"china\",\"properties\":{}}";
Object obj = JSON.parseObject(jsonString);
System.out.println(obj);
System.out.println(obj.getClass());
}
}

输出

1
2
3
4
5
6
7
8
9
构造函数
setName
getProperties
getAddress
getAge
getName
getProperties
{"name":"Flow","age":0}
class com.alibaba.fastjson.JSONObject

可以看到调用了构造函数,public属性变量的setter函数,所有变量的getter函数,getProperties还被调用了两次,最后得到的obj是JSONObject类型,这算反序列化失败了

现在把语句改成

1
Object obj = JSON.parseObject(jsonString,human.class);

输出变成,这样就算成功了

1
2
3
4
5
构造函数
setName
getProperties
human@2cdf8d8a
class human

我问ai为什么,它说

是的,将代码改为 Object obj = JSON.parseObject(jsonString, human.class); 后,Fastjson 能够成功将 JSON 反序列化为 human 类的对象。以下是具体原因和机制分析:

1. 显式指定目标类的作用

通过 JSON.parseObject(jsonString, human.class) 明确告知 Fastjson 目标类,此时 Fastjson 的行为会发生变化:

  • 绕过 @type 的自动解析:即使 JSON 中包含 @type 字段,Fastjson 也会优先使用你传入的 human.class 作为目标类。
  • 直接实例化 human 对象:Fastjson 会通过反射创建 human 类的实例,并尝试将 JSON 字段映射到该类的字段或 setter 方法。

2. 原代码失败的原因

原代码 JSON.parseObject(jsonString) 未指定目标类,Fastjson 会尝试根据 JSON 中的 @type 字段动态加载类:

  • 依赖 @type 的类加载机制@type 字段的值 human 需要对应一个全限定类名(如 com.example.human),且该类必须在类路径中可访问。
  • 原代码的问题:
    • 如果 human 类未正确定义或未被正确加载,Fastjson 无法找到该类,会回退到通用 JSONObject
    • 即使 human 类存在,某些 Fastjson 版本默认禁用 @type 功能(出于安全考虑),导致动态类加载失败。

parse与parseObject区别

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

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

所以使用parse()的时候他会自动反序列化得到特定的类,不用想parseObject()一样指定第二个参数,看看效果

现在的代码是

1
2
3
4
5
6
7
8
9
public class humanUnSerialize {
public static void main(String[] args) {
String jsonString ="{\"@type\":\"human\",\"age\":20," +
"\"name\":\"Flow\",\"address\":\"china\",\"properties\":{}}";
Object obj = JSON.parse(jsonString);
System.out.println(obj);
System.out.println(obj.getClass());
}
}

输出

1
2
3
4
5
构造函数
setName
getProperties
human@2cdf8d8a
class human

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

Fastjson反序列化原理

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

fastjson会对下列要求的setter和getter方法进行调用

满足条件的setter:

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

满足条件的getter:

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

漏洞原理

通过fastjson反序列化漏洞,攻击者可以传入恶意构造的json内容,程序对他反序列化后得到恶意类执行力恶意类中的恶意函数,从而导致代码执行

fastjson用parse()/parseObject()反序列化的时候可以指定类型,如果刚好指定的类型有很多子类,有很多子函数,就比较有利用空间。

例如,如果指定类型为Object或JSONObject,则可以反序列化出来任意类。例如代码写Object o = JSON.parseObject(poc,Object.class)就可以反序列化出Object类或其任意子类,而Object又是任意类的父类,所以就可以反序列化出所有类。

如何触发恶意函数?

某种情况下,反序列化后会把反序列化得到的类中的构造函数,getter,setter都执行一遍,如果这三个里面存在恶意函数,这就是反序列化漏洞所在!所以触发方式就在这三个里面

所以利用反序列化漏洞的关键:

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

Poc

如果反序列化指定的类型的是Object.class,也就是代码是

1
Object obj = JSON.parseObject(jsonstring, Object.class, Feature.SupportNonPublicField);

反序列化得到的类的构造函数、所有属性的setter方法、properties私有属性的getter方法都会被调用

现在做个尝试,改一下human.java里面的getter函数

1
2
3
4
5
public Properties getProperties() throws IOException {
System.out.println("getProperties");
Runtime.getRuntime().exec("open -a calculator");
return properties;
}

再执行代码就跳出计算机

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


public class humanUnSerialize {
public static void main(String[] args) {
String jsonString ="{\"@type\":\"human\",\"age\":20," +
"\"name\":\"Flow\",\"address\":\"china\",\"properties\":{}}";
Object obj = JSON.parseObject(jsonString,Object.class,Feature.SupportNonPublicField);
System.out.println(obj);
System.out.println(obj.getClass());
}
}

当前反序列化里面指定的类是Object.class,他是所有类的父类,所以他的子类的human的getter函数存在恶意函数,当@type指向human类是反序列化就会触发漏洞

如果你知道这个类本事就有问题,直接指定类名也是可以的

1
Object obj = JSON.parseObject(jsonString,human.class,Feature.SupportNonPublicField);

看代码触发语句就是

参考

https://drun1baby.top/2022/08/04/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Fastjson%E7%AF%8701-Fastjson%E5%9F%BA%E7%A1%80

 评论
评论插件加载失败
正在加载评论插件
由 Hexo 驱动 & 主题 Keep