转载

Fastjson 1.2.24 反序列化漏洞深度分析

作者:天融信阿尔法实验室

公众号: https://mp.weixin.qq.com/s/vsFRpyPTmj-h3kk6KhEfeg

前言

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/<T> clazz)三个方法从代码层面上来看,究竟有何不同?

2、使用TemplatesImpl攻击调用链路构造poc时,为什么一定需要构造 _tfactory 以及 _name 字段?

3、 _outputProperties 与其getter方法getOutputProperties()方法名字并不完全一致是如何解决的?

除此之外,本文在介绍TemplatesImpl攻击调用链路时,以模拟寻找漏洞利用链的思路,从最终的执行点开始向上寻找入口,模拟还原出挖掘这个TemplatesImpl利用链的完整过程。

漏洞分析

关于parse (String text) 、parseObject(String text)、 parseObject(String text, Class/<T> clazz)三个方法,我们进行一个测试

Fastjson 1.2.24 反序列化漏洞深度分析

FastJsonTest类中变量以及其setter/getter关系如下表

public String t1 private int t2 private Boolean t3 private Properties t4 private Properties t5
setter
getter

接下来,我们分别使用下图三种方式分别将JSON字符串反序列化成Java对象

Fastjson 1.2.24 反序列化漏洞深度分析

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);这种方式

Fastjson 1.2.24 反序列化漏洞深度分析

结果:

setT1() 、setT2() 、getT4() 、setT5() 被调用

JSON.parse(jsonstr)最终返回FastJsonTest类的对象

接着我们运行下Object obj = JSON.parseObject(jsonstr, FastJsonTest.class);

Fastjson 1.2.24 反序列化漏洞深度分析

结果:

与JSON.parse(jsonstr);这种方式一样setT1() 、setT2() 、getT4() 、setT5() 被调用

JSON.parse(jsonstr)最终返回FastJsonTest类的对象

最后我们运行下Object obj = JSON.parseObject(jsonstr);

Fastjson 1.2.24 反序列化漏洞深度分析

结果:

这次结果与上两次大不相同,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()方法时情景如下图

Fastjson 1.2.24 反序列化漏洞深度分析

此时的调用链如下图

Fastjson 1.2.24 反序列化漏洞深度分析

此时传入 JavaBeanInfo.build() 方法的参数值如下图

Fastjson 1.2.24 反序列化漏洞深度分析

再来看下 JSON.parseObject(jsonstr,FastJsonTest.class) 这种方式,当程序执行到 JavaBeanInfo.build() 方法时情景如下图

Fastjson 1.2.24 反序列化漏洞深度分析

此时的调用链如下图

Fastjson 1.2.24 反序列化漏洞深度分析

此时传入 JavaBeanInfo.build() 方法的参数值如下图

Fastjson 1.2.24 反序列化漏洞深度分析

二者执行到 JavaBeanInfo.build() 方法时调用链对比如下

Fastjson 1.2.24 反序列化漏洞深度分析

可见二者后面的调用链是完全一样的。二者不同点在于调用 JavaBeanInfo.build() 方法时传入clazz参数的来源不同:

JSON.parseObject(jsonstr, FastJsonTest.class) 在调用 JavaBeanInfo.build() 方法时传入的clazz参数源于parseObject方法中第二个参数中指定的“FastJsonTest.class”。

JSON.parse(jsonstr); 这种方式调用 JavaBeanInfo.build() 方法时传入的clazz参数获取于json字符串中/@type字段的值。

Fastjson 1.2.24 反序列化漏洞深度分析

关于 JSON.parse(jsonstr); 从json字符串中/@type字段获取clazz参数,具体代码如下

Fastjson 1.2.24 反序列化漏洞深度分析

程序通过解析传入的json字符串的/@type字段值来获取之后传入JavaBeanInfo.build()方法的clazz参数

因此,只要Json字符串的/@type字段值与JSON.parseObject(jsonstr,FastJsonTest.class);中第二个参数中类名一致,见下图

Fastjson 1.2.24 反序列化漏洞深度分析

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() 方法可见如下代码

Fastjson 1.2.24 反序列化漏洞深度分析

程序从clazz(目标类对象)中通过getMethods获取本类以及父类或者父接口中所有的公共方法,接着进行循环判断这些方法是否可以加入fieldList中以便后续处理

条件一、方法名需要长于4

Fastjson 1.2.24 反序列化漏洞深度分析

条件二、不是静态方法

Fastjson 1.2.24 反序列化漏洞深度分析

条件三、以get字符串开头,且第四个字符需要是大写字母

Fastjson 1.2.24 反序列化漏洞深度分析

条件四、方法不能有参数传入

Fastjson 1.2.24 反序列化漏洞深度分析

条件五、继承自Collection || Map || AtomicBoolean || AtomicInteger || AtomicLong

Fastjson 1.2.24 反序列化漏洞深度分析

条件六、此getter不能有setter方法(程序会先将目标类中所有的setter加入fieldList列表,因此可以通过读取fieldList列表来判断此类中的getter方法有没有setter)

Fastjson 1.2.24 反序列化漏洞深度分析

问题三解答

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) 实现如下

Fastjson 1.2.24 反序列化漏洞深度分析

parseObject(String text)其实就是执行了parse(),随后将返回的Java对象通过JSON.toJSON()转为 JSONObject对象。

JSON.toJSON()方法会将目标类中所有getter方法记录下来,见下图

Fastjson 1.2.24 反序列化漏洞深度分析

随后通过反射依次调用目标类中所有的getter方法

Fastjson 1.2.24 反序列化漏洞深度分析

完整的调用链如下

Fastjson 1.2.24 反序列化漏洞深度分析

总结:

上文例子中, JSON.parse(jsonstr)JSON.parseObject(jsonstr, FastJsonTest.class) 可以认为是完全一样的,而parseObject(String text)是在二者的基础上又执行了一次JSON.toJSON()

parse(String text)、parseObject(String text)与parseObject(String text, Class/<T> clazz)目标类Setter/Getter调用情况

parse(String text) parseObject(String text) parseObject(String text, Class/<T> 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方法

Fastjson 1.2.24 反序列化漏洞深度分析

其中455行调用 _class[_transletIndex] 的newInstance( )方法来实例化对象的操作

我们看一下 _class[_transletIndex] 是如何获取的,是否可以控制

_class_transletIndex 值皆由451行处defineTransletClasses()方法中获取

Fastjson 1.2.24 反序列化漏洞深度分析

我们跟入defineTransletClasses()方法中一探究竟

Fastjson 1.2.24 反序列化漏洞深度分析

在defineTransletClasses()方法中,首先在393行判断 _bytecodes 值是否为空

Fastjson 1.2.24 反序列化漏洞深度分析

值得注意的是, _bytecodes 变量是TemplatesImpl类的成员变量

Fastjson 1.2.24 反序列化漏洞深度分析

因此 _bytecodes 变量可以在构造json字符串时传入,在构造poc时属于可控变量

_bytecodes 变量非空值时,程序将会继续执行至下图红框处

Fastjson 1.2.24 反序列化漏洞深度分析

此时,需要满足 _tfactory 变量不为null,否则导致程序异常退出。这就是为什么公开的poc中需要设置设置 _tfactory 为{}的原因。因为 _tfactory 为私有变量,且无setter方法,这里需要指定 Feature.SupportNonPublicField 参数来为_tfactory赋值

接下来,程序将会把 _bytecodes 变量中的值循环取出并通过loader.defineClass处理后赋值给 _class[i]

Fastjson 1.2.24 反序列化漏洞深度分析

我们首先来看下loader.defineClass方法是什么

Fastjson 1.2.24 反序列化漏洞深度分析

可见,loader.defineClass方法其实就是对ClassLoader. defineClass的重写。defineClass方法可以从传入的字节码转化为Class

回头分析下上述流程

Fastjson 1.2.24 反序列化漏洞深度分析

_bytecodes 变量非空值时,程序将会把 _bytecodes 数组中的值循环取出,使用loader.defineClass方法从字节码转化为Class对象,随后后赋值给_class[i]。

如果此时的class为main class, _transletIndex 变量值则会是此时 _bytecodes 数组中的下标值

因此当我们构造出 _bytecodes:[evilCode] 这样的json字符串(evilCode字符串为我们构造的恶意类的字节码)后,程序会将evilCode化为Class对象后赋值给_class[0]

现在回到getTransletInstance()方法中

Fastjson 1.2.24 反序列化漏洞深度分析

此时的 _class[_transletIndex] 即为我们构造传入的evilCode类

程序通过调用evilCode类的newInstance()方法来实例化对象的操作,这将导致我们构造的evilCode类中的恶意代码被执行

但在此之前,需要在poc构造json字符串时使得成员变量 _name 不为空,否则程序还未执行到将evilCode类实例化就提前return

Fastjson 1.2.24 反序列化漏洞深度分析

注意:由于私有变量 _name 没有setter方法,在反序列化时想给这个变量赋值则需要使用Feature.SupportNonPublicField参数。

在分析完存在漏洞的getTransletInstance方法,我们需要找到一条调用链,这条调用链需要在使用fastjson处理json字符串时成功串连到存在漏洞的getTransletInstance方法上。

我们继续向上跟踪代码

Fastjson 1.2.24 反序列化漏洞深度分析

com/sun/org/apache/xalan/internal/xsltc/trax/TemplatesImpl.javanewTransformer() 方法中调用了getTransletInstance()

继续向上跟踪

Fastjson 1.2.24 反序列化漏洞深度分析

com/sun/org/apache/xalan/internal/xsltc/trax/TemplatesImpl.javagetOutputProperties() 方法中调用了newTransformer()

getOutputProperties()方法为 _outputProperties 成员变量的getter方法

Fastjson 1.2.24 反序列化漏洞深度分析

细心的读者可能会发现,成员变量 _outputProperties 与其getter方法getOutputProperties()方法名字并不完全一致,多了一个下划线,fastjson是如何将其对应的呢?

实际上,fastjson在解析的时候调用了一个smartMatch() 方法

Fastjson 1.2.24 反序列化漏洞深度分析

在寻找 _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()方法被触发就会引起反序列化代码执行漏洞。所以说这个漏洞利用很是巧妙。

总结

针对Fastjson 1.2.24反序列化漏洞的利用方式有很多,本文由于篇幅有限仅对比较巧妙的TemplatesImpl攻击调用链路进行举例。后续将会对Fastjson历史漏洞进行详细的分析,希望大家喜欢。

原文  https://paper.seebug.org/1274/
正文到此结束
Loading...