fastjson反序列化

0x01前言

最近工作比较忙,给自己休整了几天,正好过两天就周末了要出门也没空学啥,所以打算把学习任务提前一下。原谅自己学习进度太慢了。。。

参考文章依旧是infer师傅的文章:https://infernity.top/2025/02/25/fastjson%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/

还有一个师傅的文章写的也很好:Java安全学习——Fastjson反序列化漏洞

0x02Fastjson的序列化和反序列化

fastjson 是阿里巴巴开发的 java语言编写的高性能 JSON 库,用于将数据在 Json 和 Java Object之间相互转换。它没有用java的序列化机制,而是自定义了一套序列化机制。

在fastjson中提供了两种接口函数

  • JSON#toJSONString()实现对象的序列化操作
  • JSON#parseObject()/JSON#parse()实现对象的反序列化操作

但是对于Fastjson来说,并不是所有的java对象都能被序列化为JSON,只有JavaBean格式的对象才能被Fastjson转化成JSON格式

我们写个demo来看看序列化和反序列化流程的走向

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
class Person {
//使用Alt+Insert键可以快速生成属性的getter和setter方法
public String name;
public int age;

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;
}
}

然后我们写个序列化和反序列化的操作

先看看序列化的操作

1
2
3
4
5
6
7
8
9
10
11
12
public class Demo {
public static void main(String[] args) {
Person p = new Person();
p.setName("John");
p.setAge(22);

//序列化
System.out.println("----------序列化操作----------");
String json = JSON.toJSONString(p);
System.out.println(json);
}
}

输出

1
2
3
4
5
6
执行了setName方法
执行了setAge方法
----------序列化操作----------
执行了getAge方法
执行了getName方法
{"age":22,"name":"John"}

在toJSONString()方法处打个断点调试一下

首先进入了第一个toJSOINString方法

1
2
3
public static String toJSONString(Object object) {
return toJSONString(object, emptyFilters);
}

随后进入里面的另一个toJSONString()方法

1
2
3
public static String toJSONString(Object object, SerializeFilter[] filters, SerializerFeature... features) {
return toJSONString(object, SerializeConfig.globalInstance, filters, (String)null, DEFAULT_GENERATE_FEATURE, features);
}

然后又是一个toJSONString()方法

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

里面的就是具体的序列化流程了,这个就不深究了

我们再来看看反序列化流程

先看看JSON#parseObject()方法的参数

1
2
3
public static <T> T parseObject(String text, Class<T> clazz) {
return parseObject(text, clazz, new Feature[0]);
}

接收JSON字符串和原生类作为参数

序列化看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Demo {
public static void main(String[] args) {
Person p = new Person();
p.setName("John");
p.setAge(22);

//序列化
System.out.println("----------序列化操作----------");
String json = JSON.toJSONString(p);
System.out.println(json);

//反序列化
System.out.println("----------反序列化操作----------");
Person unserjson = JSON.parseObject(json, Person.class);
System.out.println(unserjson);
}
}

image-20250717152311981

其实到这里的话就很清楚了,在反序列化的时候,JSON#parseObject()方法会调用原生类的Setter方法。

如果我们反序列化时不指定特定的类,那么Fastjosn就默认将一个JSON字符串反序列化为一个JSONObject。需要注意的是,对于类中private类型的属性值,Fastjson默认不会将其序列化和反序列化。

不过在上面的例子中可以看出,JSON#parseObject方法调用的时候我们是给它固定了原生类为Person.class,那么如果在实际环境中,有那么多的类的话,此时程序如何知道自己需要反序列化什么类的对象呢?这时候就需要用到一个注解@type了

将JSON反序列化为原始的类的方法有两种

  • 第一种是在序列化的时候,在toJSONString方法中添加额外的属性SerializerFeature.WriteClassName,将对象类型一并序列化,我们测试一下

image-20250717153201784

结果就是Fastjson在JSON字符串中添加了一个@type字段,这个用于标识对象所属的类

在反序列化的时候,parse()方法就会根据@type字段去转化成原来的类

image-20250717153456691

  • 第二种方法是在反序列化的时候,在parseObject()方法中手动指定对象的类型

0x02Fastjson中的@type

我们介绍一下这里的@type字段

@type是fastjson中的一个特殊注解,用于标识JSON字符串中的某个属性是哪个Java对象的类型。具体来说,当fastjson从JSON字符串反序列化为Java对象时,如果JSON字符串中包含@type属性,fastjson会根据该属性的值来确定反序列化后的Java对象的类型。

再来看看下面两个测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Demo {
public static void main(String[] args) {

//反序列化
System.out.println("----------反序列化操作1----------");
String ser_json1 = "{\"name\":\"wanth3f1ag\",\"age\":22}";
JSON.parseObject(ser_json1);

System.out.println("----------反序列化操作2----------");
String ser_json2 = "{\"@type\":\"SerializeChains.FastjsonSer.Person\",\"name\":\"wanth3f1ag\",\"age\":22}";
JSON.parseObject(ser_json2);
}
}

image-20250717154235481

可以看到在没有指定@type字段的时候,程序并不知道该把JSON字符串序列化成哪个类型的对象,当我们用@type属性后,就能正常的将JSON字符串按照Person类去反序列化回对象

同时这样也会调用到相应的setter方法

那么这里就引出一个问题,如果这里的@type没有进行特殊的处理和检查,我们是否可以利用这个属性去指定一些恶意类去实例化利用他们呢?

例如DNS请求的类

1
{"@type":"java.net.InetAddress","val":"b3jv10.dnslog.cn"}

image-20250717160002116

成功接收到DNS请求

那么同样的,我们的类中的getter或setter方法包含恶意代码的话也就能执行

image-20250717160902828

因此,只要我们能找到一个合适的Java Bean,其setter或getter存在可控参数,则有可能造成任意命令执行。

0x03Fastjson <= 1.2.24-Chains

我们来看最开始出现序列化的版本Fastjson<=1.2.24,在这个版本中是默认支持@type这个属性的

这个版本有两条利用链JdbcRowSetImpl利用链和Templateslmpl利用链

JdbcRowSetImpl利用链

JdbcRowSetImpl利用链最终的结果是导致JNDI注入,需要结合JDBC的攻击手法去利用,这个是通用性最强的利用方式

JdbcRowSetImpl利用链的重点就在怎么调用autoCommit的set方法,而fastjson反序列化的特点就是会自动调用到类的set方法,所以会存在这个反序列化的问题。

我们看一下