前言

终于开始fastjson的学习了,这两天放松的有点太严重了,哈哈哈。

Fastjson简介

fastjson是一个java库,主要作用:可以把java对象转换为json的格式,也可以将json的格式转换为java对象。
提供了两个主要的接口来分别实现序列化和反序列化的操作
JSON.toJSONString将java对象转化为json对象,序列化的过程。
JSON.parseObject/JSON.parse将json对象重新变回java对象;反序列化过程。
这里可以将json理解成一个字符串。(这样就和php反序列化类似)

从代码角度分析

序列化代码的实现

这里我们来写一下简单的代码来了解一下具体的序列化和反序列化的过程。
首先引入依赖

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

这里来写一个简单的Student类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Student {  
private String name;
private int age;
public Student() {
System.out.println("Student constructor");
}

public int getAge() {
System.out.println("Student getAge");
return age;
}
public void setAge(int age) {
System.out.println("Student setAge");
this.age = age;
}
public String getName() {
System.out.println("Student getName");
return name;
}
public void setName(String name) {
System.out.println("Student setName");
this.name = name;
}
}

然后我们尝试对它进行实例化调用并序列化成一个json的字符串

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

public class StudentSerialize {
public static void main(String[] args) {
Student student = new Student();
student.setAge(22);
student.setName("红烧花园宝宝");
String json = JSON.toJSONString(student, SerializerFeature.WriteClassName);
System.out.println(json);
}
}

我们来打个断点分析一下
image.png
首先这里看到java对象变成json字符串的具体对象是Student
image.png
看到这里在static变量里是有一个值为@type的DEFAULT_TYPE_KEY,这个是很重要的,我们接下来会做一个解释。
image.png
这里调用完toString,序列化过程也就差不多了。
主要完成序列化的点在
String json = JSON.toJSONString(student, SerializerFeature.WriteClassName);

  1. 第一个参数:不用过多介绍,就是需要序列化的类
  2. 第二个参数:在序列化(Java 对象转 JSON)时,将 Java 对象的数据类型(类名)写入到 JSON 字符串中。
  • 当我们想要把json对象还原成java对象时,我们就可以通过’@type‘来知道具体还原的对象。总的来说就是方便反序列化。
    image.png

反序列化代码的实现

Studentunserialize

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

public class StudentunSeriallize {
public static void main(String[] args) {
String json = "{\"@type\":\"Student\"\"name\":\"红烧花园宝宝\",\"age\":18}";
Student student = JSON.parseObject(json, Student.class, Feature.SupportNonPublicField);
System.out.println(student);
System.out.println(student.getAge());
System.out.println(student.getClass().getName());


}
}

image.png

知识点

反序列化时的Feature.SupportNonPublicField参数的作用

首先这个参数的作用:带上这个参数它可以还原私有属性。
具体的效果
当我们将Student的age的setage方法去掉时,因为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
public class Student {  
private String name;
private int age = 11;
public Student() {
System.out.println("Student constructor");
}

public int getAge() {
System.out.println("Student getAge");
return age;
}
// public void setAge(int age) {
// System.out.println("Student setAge");
// this.age = age;
// } //私有属性也不会写这个
public String getName() {
System.out.println("Student getName");
return name;
}
public void setName(String name) {
System.out.println("Student setName");
this.name = name;
}
}

不加Feature.SupportNonPublicField的执行结果
image.png
加上Feature.SupportNonPublicField的执行结果
image.png
总之当我们使用JSON.parseObject的时候带上这个参数,它就可以让我们还原私有属性。

仅使用JSON.parseObject(json)

这里测试的是不指定类型的反序列化
首先改一下Student类,我是加了个私有属性properties

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

public class Student {
private String name;
private int age = 11;
private Properties properties;
public Student() {
System.out.println("Student constructor");
}

public int getAge() {
System.out.println("Student getAge");
return age;
}
// public void setAge(int age) {
// System.out.println("Student setAge");
// this.age = age;
// }
public String getName() {
System.out.println("Student getName");
return name;
}
public void setName(String name) {
System.out.println("Student setName");
this.name = name;
}

public Properties getProperties() {
System.out.println("Student getProperties");
return null;
}
// public void setProperties(Properties properties) {
// System.out.println("Student setProperties");
// }
}

然后反序列化类的内容为

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

public class StudentunSeriallize {
public static void main(String[] args) {
String json = "{\"@type\":\"Student\",\"name\":\"红烧花园宝宝\",\"age\":18,\"properties\":{}}";
Object student = JSON.parseObject(json); //这里我们不指定数据类型
System.out.println(student);
System.out.println(student.getClass().getName());

}
}

输出的内容
image.png
这里可以看到调用了,构造方法,setter方法和getter方法,同时properties的get方法调用了两次。然后这里的反序列化是没有成功的,最后返回的是JSONObject。
image.png
如果我们指定完数据类型就会成功反序列化。

parse和parseObject的区别

parse和parseObject的主要区别前者返回的是实际类型的对象,而后者返回的是JSONObject对象。
在反序列化时,parse会调用目标类的getter和部分特定的setter,而parseObject由于将对象转换成jsonObject对象,会执行JSON.toJSON(obj),所以会调用所有的setter和getter方法。
总之,parse可以反序列化可以直接得到特定的类,而不需要像parseObject一样返回JSONObject对象(还要去设置第二个参数来进行所需要类型的返回)。
image.png

Fastjson的漏洞原理

根据以上的分析,我们了解到,@type的值是我们需要还原的类,而在执行parse或parseObject的时候会调用到这个类的setter和getter方法(需要满足特定的条件)。

  1. setter
  • 非静态函数
  • 返回类型为void或当前类
  • 参数个数为1个
  1. getter
  • 非静态方法
  • 无参数
  • 返回值类型继承自Collection或Map或AtomicBoolean或AtomicInteger或AtomicLong

漏洞原理

Fastjson的序列化和反序列化和原生的java原生的序列化和反序列化是没有关系的。
Fastjson 在反序列化 JSON 字符串时,允许通过 @type 字段指定任意类,并且在还原对象的过程中,会自动调用该类的 setter 方法(或构造函数、getter 方法)。如果被调用的方法中包含恶意代码(或能触发恶意逻辑),就会导致远程代码执行(RCE)。

  • 注意点:我们知道在序列化和反序列化的时候是可以指定数据类型的,如果类型太大或者是有很多子类,那么我们就会有很大的利用空间。

第一种(指定数据类型):Student obj = JSON.parseObject(text, Student.class);
如果Student有能够被调用的setter或者getter方法,并且存在恶意方法的话,就会成为漏洞点。
第二种(未指定数据类型):Object obj = JSON.parseObject(text, Object.class);
如果text的子类的能被调用的getter或者setter中存在恶意方法,就会造成漏洞。

poc的写法

一般的poc写法为

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

反序列化的这个类中要存在两个条件:

  1. 类的setter和getter中要有恶意方法
  2. 属性是可以控制的

漏洞的实现

Student

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

public class Student {
private String name;
private int age = 11;
private Properties properties;
public Student() {
System.out.println("Student constructor");
}

public int getAge() {
System.out.println("Student getAge");
return age;
}
// public void setAge(int age) {
// System.out.println("Student setAge");
// this.age = age;
// }
public String getName() {
System.out.println("Student getName");
return name;
}
public void setName(String name) {
System.out.println("Student setName");
this.name = name;
}

public Properties getProperties() throws IOException {
System.out.println("Student getProperties");
Runtime.getRuntime().exec("calc");
return null;
}
// public void setProperties(Properties properties) {
// System.out.println("Student setProperties");
// }
}

StudentunSeriallize

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

public class StudentunSeriallize {
public static void main(String[] args) {
String json = "{\"@type\":\"Student\",\"name\":\"红烧花园宝宝\",\"age\":18,\"properties\":{}}";
Object student = JSON.parse(json);
System.out.println(student);
System.out.println(student.getClass().getName());

}
}

image.png
getProperties方法被调用触发了恶意代码实现命令执行。

漏洞的分析

执行点就在这里,就不细看了
image.png

参考

Java反序列化Fastjson篇01-FastJson基础 | Drunkbaby’s Blog