前言
FastJson是alibaba的一款开源JSON解析库,可用于将Java对象转换为其JSON表示形式,也可以用于将JSON字符串转换为等效的Java对象。近几年来fastjson漏洞层出不穷,本文将会谈谈近几年来fastjson
RCE漏洞的源头:17年fastjson爆出的1.2.24反序列化漏洞。以这个漏洞为基础,详细分析fastjson漏洞的一些细节问题。
关于Fastjson 1.2.24反序列化漏洞,自从17年以来已经有很多人分析过了,一些基础内容本文就不再陈述了。此次漏洞简单来说,就是Fastjson通过parseObject/parse将传入的字符串反序列化为Java对象时由于没有进行合理检查而导致的
本文将着重分析一下这个漏洞没有被详细介绍过的细节问题,如下:
1、parseObject(String text) 、parse (String text)、 parseObject(String text, Class\ clazz)三个方法从代码层面上来看,究竟有何不同?
2、使用TemplatesImpl攻击调用链路构造poc时,为什么一定需要构造_tfactory以及_name字段?
3、_outputProperties与其getter方法getOutputProperties()方法名字并不完全一致是如何解决的?
除此之外,本文在介绍TemplatesImpl攻击调用链路时,以模拟寻找漏洞利用链的思路,从最终的执行点开始向上寻找入口,模拟还原出挖掘这个TemplatesImpl利用链的完整过程。
漏洞分析
关于parse (String text) 、parseObject(String text)、 parseObject(String text, Class\ clazz)三个方法,我们进行一个测试
FastJsonTest类中变量以及其setter/getter关系如下表
| | public String t1 | private int t2 | private Boolean t3 | private Properties t4 | private Properties t5 |
| —— | —————- | ————– | —————— | ——————— | ——————— |
| setter | 有 | 有 | 无 | 无 | 有 |
| getter | 有 | 有 | 有 | 有 | 有 |
接下来,我们分别使用下图三种方式分别将JSON字符串反序列化成Java对象
1、Object obj = JSON.parse(jsonstr);
2、Object obj = JSON.parseObject(jsonstr, FastJsonTest.class);
3、Object obj = JSON.parseObject(jsonstr);
首先我们运行一下Object obj = JSON.parse(jsonstr);这种方式
结果:
setT1() 、setT2() 、getT4() 、setT5() 被调用
JSON.parse(jsonstr)最终返回FastJsonTest类的对象
接着我们运行下Object obj = JSON.parseObject(jsonstr, FastJsonTest.class);
与JSON.parse(jsonstr);这种方式一样setT1() 、setT2() 、getT4() 、setT5() 被调用
JSON.parse(jsonstr)最终返回FastJsonTest类的对象
最后我们运行下Object obj = JSON.parseObject(jsonstr);
结果:
这次结果与上两次大不相同,FastJsonTest类中的所有getter与setter都被调用了,并且JSON.parseObject(jsonstr);返回一个JSONObject对象
通过上文运行结果,不难发现有三个问题
1. 使用JSON.parse(jsonstr);与JSON.parseObject(jsonstr, FastJsonTest.class);两种方式执行后的返回结果完全相同,且FastJsonTest类中getter与setter方法调用情况也完全一致,parse(jsonstr)与parseObject(jsonstr, FastJsonTest.class)有何关联呢?
2. 使用JSON.parse(jsonstr);与JSON.parseObject(jsonstr, FastJsonTest.class);两种方式时,被调用的getter与setter方法分别为setT1()、setT2()、setT5()、getT4()。FastJsonTest类中一共有五个getter方法,分别为getT1()、getT2()、getT3()、getT4()、getT5(),为什么仅仅getT4被调用了呢?
3. JSON.parseObject(jsonstr);为什么返回值为JSONObject类对象,且将FastJsonTest类中的所有getter与setter都被调用了
问题一解答
经过调试可以发现,无论使用JSON.parse(jsonstr);或是JSON.parseObject(jsonstr,
FastJsonTest.class);方式解析json字符串,程序最终都会调用位于com/alibaba/fastjson/util/JavaBeanInfo.java中的JavaBeanInfo.build()
方法来获取并保存目标Java类中的成员变量以及其对应的setter、getter
首先来看下JSON.parse(jsonstr)这种方式,当程序执行到JavaBeanInfo.build()
方法时情景如下图
此时的调用链如下图
此时传入JavaBeanInfo.build() 方法的参数值如下图
再来看下JSON.parseObject(jsonstr,
FastJsonTest.class)这种方式,当程序执行到JavaBeanInfo.build() 方法时情景如下图
此时的调用链如下图
此时传入JavaBeanInfo.build() 方法的参数值如下图
二者执行到JavaBeanInfo.build() 方法时调用链对比如下
可见二者后面的调用链是完全一样的。二者不同点在于调用JavaBeanInfo.build()
方法时传入clazz参数的来源不同:
JSON.parseObject(jsonstr, FastJsonTest.class)在调用JavaBeanInfo.build()
方法时传入的clazz参数源于parseObject方法中第二个参数中指定的“FastJsonTest.class”。
JSON.parse(jsonstr);这种方式调用JavaBeanInfo.build()
方法时传入的clazz参数获取于json字符串中\@type字段的值。
关于JSON.parse(jsonstr);从json字符串中\@type字段获取clazz参数,具体代码如下
程序通过解析传入的json字符串的\@type字段值来获取之后传入JavaBeanInfo.build()
方法的clazz参数
因此,只要Json字符串的\@type字段值与JSON.parseObject(jsonstr,
FastJsonTest.class);中第二个参数中类名一致,见下图
JSON.parse(jsonstr)与JSON.parseObject(jsonstr,FastJsonTest.class)这两种方式执行的过程与结果是完全一致的。二者唯一的区别就是获取clazz参数的途径不同
问题二解答
> 使用JSON.parse(jsonstr)与JSON.parseObject(jsonstr, FastJsonTest.class)两种方式时,被调用的getter与setter方法分别为setT1()、setT2()、setT5()、getT4()。FastJsonTest类中一共有五个getter方法,分别为getT1()、getT2()、getT3()、getT4()、getT5(),为什么仅仅getT4被调用了呢?
这个问题要从JavaBeanInfo.build() 方法中获取答案:
通过上文的分析可以发现,程序会使用JavaBeanInfo.build()
方法对传入的json字符串进行解析。在JavaBeanInfo.build()
方法中,程序将会创建一个fieldList数组来存放后续将要处理的目标类的 setter
方法及某些特定条件的 getter
方法。通过上文的结果可见,目标类中所有的setter方法都可以被调用,但只有getT4()这一个getter被调用,那么到底什么样的getter
方法可以满足要求并被加入fieldList数组中呢?
在JavaBeanInfo.build() 方法可见如下代码
程序从clazz(目标类对象)中通过getMethods获取本类以及父类或者父接口中所有的公共方法,接着进行循环判断这些方法是否可以加入fieldList中以便后续处理
条件一、方法名需要长于4
条件二、不是静态方法
条件三、以get字符串开头,且第四个字符需要是大写字母
条件四、方法不能有参数传入
条件五、继承自Collection \|\| Map \|\| AtomicBoolean \|\| AtomicInteger \|\|
AtomicLong
条件六、此getter不能有setter方法(程序会先将目标类中所有的setter加入fieldList列表,因此可以通过读取fieldList列表来判断此类中的getter方法有没有setter)
问题三解答
JSON.parseObject(jsonstr)为什么返回值为JSONObject类对象,且将FastJsonTest类中的所有getter与setter都被调用了
通过上文的分析可以发现,JSON.parse(jsonstr)与JSON.parseObject(jsonstr,
FastJsonTest.class)两种方式从执行流程几乎一样,结果也完全相同;然而使用JSON.parseObject(jsonstr)这种方式,执行的结果与返回值却与前两者不同:JSON.parseObject(jsonstr)返回值为JSONObject类对象,且将FastJsonTest类中的所有getter与setter都被调用。
通过阅读源码可以发现JSON.parseObject(String text)实现如下
parseObject(String text)其实就是执行了parse(),随后将返回的Java对象通过JSON.toJSON()转为
JSONObject对象。
JSON.toJSON()方法会将目标类中所有getter方法记录下来,见下图
随后通过反射依次调用目标类中所有的getter方法
完整的调用链如下
总结:
上文例子中,JSON.parse(jsonstr)与JSON.parseObject(jsonstr, FastJsonTest.class)可以认为是完全一样的,而parseObject(String text)是在二者的基础上又执行了一次JSON.toJSON()
parse(String text)、parseObject(String text)与parseObject(String text, Class\ clazz)目标类Setter\\Getter调用情况
| | parse(String text) | parseObject(String text) | parseObject(String text, Class\ clazz) |
| ————– | —————— | ———————— | —————————————— |
| Setter调用情况 | 全部 | 全部 | 全部 |
| Getter调用情况 | 部分 | 部分 | 全部 |
此外,如果目标类中私有变量没有setter方法,但是在反序列化时仍想给这个变量赋值,则需要使用Feature.SupportNonPublicField参数。(在下文中,为TemplatesImpl类中无setter方法的私有变量_tfactory以及_name赋值运用到的就是这个知识点)
TemplatesImpl攻击调用链路
针对于上文的分析可以发现,无论使用哪种方式处理JSON字符串,都会有机会调用目标类中符合要求的Getter方法
如果一个类中的Getter方法满足调用条件并且存在可利用点,那么这个攻击链就产生了。
TemplatesImpl类恰好满足这个要求:
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl中存在一个名为_outputPropertiesget的私有变量,其getter方法中存在利用点,这个getter方法恰好满足了调用条件,在JSON字符串被解析时可以调用其在调用FastJson.parseObject()序列化为Java对象时会被调用,下面我们详细说明一下:
首先我们从漏洞点开始,一层层往入口分析:首先看一下TemplatesImpl类中的getTransletInstance方法
其中455行调用_class[_transletIndex]的newInstance( )方法来实例化对象的操作
我们看一下_class[_transletIndex]是如何获取的,是否可以控制
\_class与_transletIndex值皆由451行处defineTransletClasses()方法中获取
我们跟入defineTransletClasses()方法中一探究竟
在defineTransletClasses()方法中,首先在393行判断_bytecodes值是否为空
值得注意的是,_bytecodes变量是TemplatesImpl类的成员变量
因此_bytecodes变量可以在构造json字符串时传入,在构造poc时属于可控变量
\_bytecodes变量非空值时,程序将会继续执行至下图红框处
此时,需要满足_tfactory变量不为null,否则导致程序异常退出。这就是为什么公开的poc中需要设置设置_tfactory为{
}的原因。因为_tfactory为私有变量,且无setter方法,这里需要指定Feature.SupportNonPublicField参数来为_tfactory赋值
接下来,程序将会把_bytecodes变量中的值循环取出并通过loader.defineClass处理后赋值给_class[i]
我们首先来看下loader.defineClass方法是什么
可见,loader.defineClass方法其实就是对ClassLoader.
defineClass的重写。defineClass方法可以从传入的字节码转化为Class
回头分析下上述流程
\_bytecodes变量非空值时,程序将会把_bytecodes数组中的值循环取出,使用loader.defineClass方法从字节码转化为Class对象,随后后赋值给_class[i]。
如果此时的class为main
class,_transletIndex变量值则会是此时_bytecodes数组中的下标值
因此当我们构造出_bytecodes:[evilCode]这样的json字符串(evilCode字符串为我们构造的恶意类的字节码)后,程序会将evilCode化为Class对象后赋值给_class[0]
现在回到getTransletInstance()方法中
此时的_class[_transletIndex]即为我们构造传入的evilCode类
程序通过调用evilCode类的newInstance()方法来实例化对象的操作,这将导致我们构造的evilCode类中的恶意代码被执行
但在此之前,需要在poc构造json字符串时使得成员变量_name不为空,否则程序还未执行到将evilCode类实例化就提前return
注意:由于私有变量_name没有setter方法,在反序列化时想给这个变量赋值则需要使用Feature.SupportNonPublicField参数。
在分析完存在漏洞的getTransletInstance方法,我们需要找到一条调用链,这条调用链需要在使用fastjson处理json字符串时成功串连到存在漏洞的getTransletInstance方法上。
我们继续向上跟踪代码
com/sun/org/apache/xalan/internal/xsltc/trax/TemplatesImpl.java
newTransformer()方法中调用了getTransletInstance()
继续向上跟踪
com/sun/org/apache/xalan/internal/xsltc/trax/TemplatesImpl.java
getOutputProperties()方法中调用了newTransformer()
getOutputProperties()方法为_outputProperties成员变量的getter方法
细心的读者可能会发现,成员变量_outputProperties与其getter方法getOutputProperties()方法名字并不完全一致,多了一个下划线,fastjson是如何将其对应的呢?
实际上,fastjson在解析的时候调用了一个smartMatch() 方法
在寻找_outputProperties的getter方法时,程序将下划线置空,从而产生了成员变量_outputProperties与getter方法getOutputProperties()对应的形式
FastJson与TemplatesImpl的有趣结合
首先说TemplatesImpl类。经过上文分析可发现:TemplatesImpl中存在一个反序列化利用链,在反序列化过程中,如果该类的getOutputProperties()方法被调用,即可成功触发代码执行漏洞。
再来分析下FastJson:经过上文对FastJson三种不同途径处理JSON字符串时关于getter方法被调用的条件来看,TemplatesImpl类_outputProperties成员变量的getter方法满足被调用条件。无论通过fastjson哪种方式解析json字符串,都会触发getOutputProperties()方法。
二者放在一起一拍即合:FastJson在反序列化TemplatesImpl类时会恰好触发TemplatesImpl类的getOutputProperties()方法;TemplatesImpl类的getOutputProperties()方法被触发就会引起反序列化代码执行漏洞。所以说这个漏洞利用很是巧妙。
总结
针对Fastjson1.2.24反序列化漏洞的利用方式有很多,本文由于篇幅有限仅对比较巧妙的TemplatesImpl攻击调用链路进行举例。后续将会对Fastjson历史漏洞进行详细的分析,希望大家喜欢。
来源:freebuf.com 2020-07-23 17:51:15 by: alphalab
请登录后发表评论
注册