Fastjson 基础知识篇
2024-06-30 23:53:21 # javasec # fastjson

介绍

fastjson是一款有阿里巴巴公司开发的高性能JSON解析库

JSON.toJSONString 将 Java 对象转换为 json 对象,序列化的过程。
JSON.parseObject/JSON.parse 将 json 对象重新变回 Java 对象;反序列化的过程

使用

JSON序列化

代码

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

demo.java

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package fastjson;

import com.alibaba.fastjson.JSON;

public class demo {
public static void main(String[] args) {
// 创建对象
person pysnow = new person(1);
pysnow.setName("pysnow");
pysnow.setAge(20);

// JSON序列化对象
String jsonString = JSON.toJSONString(pysnow);
System.out.printf(jsonString);
}
}

class person{
public int id;
private String name;

public person(int id) {
this.id = id;
}

private int age;

public person(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getName() {
return name;
}

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

public int getAge() {
return age;
}

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

代码调试

image.png
image.png
image.png
image.png

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
public static String toJSONString(Object object, // 
SerializeConfig config, //
SerializeFilter[] filters, //
String dateFormat, //
int defaultFeatures, //
SerializerFeature... features) {
SerializeWriter out = new SerializeWriter(null, defaultFeatures, features);

try {
JSONSerializer serializer = new JSONSerializer(out, config);

if (dateFormat != null && dateFormat.length() != 0) {
serializer.setDateFormat(dateFormat);
serializer.config(SerializerFeature.WriteDateUseDateFormat, true);
}

if (filters != null) {
for (SerializeFilter filter : filters) {
serializer.addFilter(filter);
}
}

serializer.write(object);

return out.toString();
} finally {
out.close();
}
}

具体实现逻辑为以上代码
image.png
可以看到此时的调用堆栈如上,此时多了一个static静态变量”member of fastjson”
image.png
接着我们分析代码可以知道首先他SerializeWriter对象用于存储JSON序列化出来的输出流,接着创建JSONSerializer对象,该对象是真正将Object对象转化成字符串的对象,其接受了输出流对象SerializeWriter和配置对象SerializeConfig,其中SerializeConfig对象是默认传入的不用管
image.png
接着我们可以看到toJSONString该方法传入两个参数时第二个参数为SerializerFeature… features,也就是最后一个参数
image.png
他在创建SerializeWriter对象是进行了调用,跟随SerializeWriter对象进入构造函数的
image.png
这里的逻辑就是遍历传入的features值然后与默认的feature值进行或运算,将最终的特征值赋值给this.features
image.png
最后回到toJSONString方法这里,调用了JSONSerializer的write方法传入了要序列化的对象
image.png
刚方法先获取了要序列化对象的Class对象,接着创建对于对象类的JavaBeanSerializer对象包含了对于javabean的基础信息(类名,字段等),以及所有的getter方法
最后调用该JavaBeanSerializer的write方法,该方法就是通过调用对象的getter方法获取对象的属性值然后写到字符串中
image.png
image.png
最后调用输出流对象的toString方法将SerializeWriter对象的buf值返回出来
接着我们使用两个参数进行JSON序列化
image.png

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

image.png
添加的WriteClassName参数是直接进入了SerializeWriter对象的构造方法里面,他会影响JSON序列化出来的结果,也就是会多打印一个”@type”:”类名”,主要代码逻辑在write方法里面如下
image.png
他这里会通过serializer.isWriteClassName(fieldType, object)判断是否有WriteClassName这个字段,如果有则调用writeClassName(serializer, object);方法写进serializer对象里面
image.png
最终得到的序列化字符串如下
image.png
这里的输出顺序可以发现先是age然后才是int字段,不是安装创建的顺序
image.png
这是因为JSON对象对其字符进行了排序

JSON反序列化

代码

demo.java

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
package fastjson;

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

public class demo {
public static void main(String[] args) {
ser();
unser();
}

public static void ser() {
// 创建对象
person pysnow = new person(1);
pysnow.setName("pysnow");
pysnow.setAge(20);

// JSON序列化对象
String jsonString = JSON.toJSONString(pysnow, SerializerFeature.WriteClassName);
System.out.println(jsonString);
}

public static void unser() {
String payload = "{\"@type\":\"fastjson.person\",\"age\":20,\"id\":1,\"name\":\"pysnow\"}";
person pysnow = (person) JSON.parseObject(payload, person.class);
System.out.printf(pysnow.toString());
}
}


person.java

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
44
45
46
47
48
49
50
51
52
53
54
package fastjson;

public class person{
public int id;
private String name;

private int age;

@Override
public String toString() {
return "person{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}

public person() {
}

public person(int id) {
this.id = id;
}
public person(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getName() {
return name;
}

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

public int getAge() {
return age;
}

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

image.png

代码调试

image.png
image.png
image.png
这里首先创建了DefaultJSONParser对象将各种参数放了进去,接着调用他的parseObject方法传入Class对象
image.png
image.png
这里根据@type字段创建了对于的反序列化器config.getDeserializer(type)
里面调试的deserialize方法的时候不知道怎么定位不到源代码,就直接静态分析了
image.png
image.png
很明显这里是先使用了person默认的无参构造器创建对应的对象,然后调用setter方法为对象进行赋值
image.png
这里有一些对于特殊字符串的处理如@ref,这个字段就是后面fastjson进行攻击的时候所需要用到的
image.png
这里是使用默认构造器创建对应对象的代码
至于这么给对象赋值的,由于deserialize跳不进去,并且静态分析代码又太多了,我们可以直接给setter加上输出来判断
image.png
可以看到默认不加Feature.SupportNonPublicField时private属性是通过setter方法来进行赋值的
image.png
而int字段的setter方法是存在的但是没有通过他来进行赋值,所以可以说明public属性默认是通过反射来进行赋值的,如果遇到private属性且没有Feature.SupportNonPublicField字段则就通过寻找setter方法来进行赋值
Feature.SupportNonPublicField:用来支持没有被public的属性进行反射赋值

注意问题

parseObject只加一个参数

这里我们把代码改成parseObject(String),不加第二个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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package fastjson;

public class person {
public int id;
private String name;
private int age;
private int money;

public int getMoney() {
System.out.println("getMoney");
return money;
}

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

@Override
public String toString() {
return "person{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}

public person() {
}

public person(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}

public int getId() {
System.out.println("IDget");
return id;
}

public void setId(int id) {
System.out.println("IDset");
this.id = id;
}

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


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

}

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
package fastjson;

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

public class demo {
public static void main(String[] args) {
// ser();
unser();
}

public static void ser() {
// 创建对象
person pysnow = new person(1, "pysnow", 20);

// JSON序列化对象
String jsonString = JSON.toJSONString(pysnow, SerializerFeature.WriteClassName);
System.out.println(jsonString);
}

public static void unser() {
String payload = "{\"@type\":\"fastjson.person\",\"age\":20,\"id\":1,\"name\":\"pysnow\",\"money\":200}";
JSONObject pysnow = JSON.parseObject(payload);
System.out.println(pysnow.toString());
System.out.println(pysnow.getClass().getName());
}
}


image.png
可以看到最后反序列化出来的对象是JSONObject对象
image.png
不是我们想要反序列化的对象,并且在反序列化的时候他在构建完该对象后又调用所有属性的getter方法
image.png
接着我加上第二个参数,也就是反序列化对应的Class类,可以发现成功将person对象反序列化出来了,而且在构建完对象也没有调用所有的getter方法
这里调用所有对象的getter方法的原因是parseObject在没有指定要反序列化的Class类时会自动在反序列化出后执行一次JSON.toJSON将解析出来的对象转化为JSONObject对象,而这个转化的过程就会触发对应所有字段的getter方法

parseObject于parse区别

这里前面其实也提到了一点原因,parse方法只能传入一个String参数,区别于parseObject,parse方法反序列化的对象就为原来的对象不会转化成JSONObject
image.png
所以在不知道反序列化出来的实际类时选择使用parseObject要比parse安全性要高一些,所以一般都选择使用parseObject来解析未知的JSON数据,但是这个方法在转化的时候又会调用对应的getter方法,所以标准的解析代码应该使用parseObject(String,Class),加上具体.Class对象

1
Object pysnow = JSON.parseObject(payload,具体.class);

image.png
image.png
可以看到在不知道Class类时parseObject方法实际上就是调用了parse方法来解析,并且在前面加上了一个(JSONObject)的强制转化

反序列化漏洞

fastjson在反序列化的时候会根据Json字符串里面的@type字段去寻找对于的Class对象,然后调用对应的setter或者getter方法,而对应的getter方法或者setter如果存在漏洞之内的代码就可以作为反序列化触发的起点,类似于原生反序列化的readObject

满足fastjson的getter/setter规则如下(其实就是JavaBean的规则)

满足条件的setter:

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

满足条件的getter:

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

fastjson反序列化漏洞的目的就是传入一个恶意的对象json流,然后反序列化出来的恶意对象并调用该对象的恶意函数(getter/setter/construct)。

构造恶意反序列化对象

通过前面分析可以知道parseparseObejct(String,Object.Class)时我们可以反序列化出任意的恶意对象,其中第一个parse他是默认反序列化原始对象类型,而后面那个因为指定的Class类时Obejct,而Object又是所有类的父类所有也是可以正常调用的

触发恶意方法

也就是自动触发的方法,fastjson在反序列化的时候总共会自动触发以下三个方法
construct()
setter()
getter()

若反序列化指定类型的类如Student obj = JSON.parseObject(text, Student.class);,该类本身的构造函数、setter方法、getter方法存在危险操作,则存在Fastjson反序列化漏洞;
若反序列化未指定类型的类如Object obj = JSON.parseObject(text, Object.class);,该若该类的子类的构造方法、setter方法、getter方法存在危险操作,则存在Fastjson反序列化漏洞;
PS:这里会自动调用getter的原因是当我们将反序列化类的某个返回类型为Object的属性赋值时,就会调用对应的getter

image.png
image.png
这里的Properties属性是符合getter的要求继承与hashtable的
image.png
那这里其他属性比如age,money这些没有调用getter方法是因为他们不符合getter的条件
需要返回值类型继承自Collection或Map或AtomicBoolean或AtomicInteger或AtomicLong

总结

其实就是传入一个JSON字符串,在解析反序列化的时候会自动调用getter和setter方法,然后我们传入一个服务端有的对象,其中的getter后者setter方法包含恶意代码,造成fastjson反序列化漏洞

参考文章

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