go 字符串反序列化成对象数组_Fastjson 1.2.24反序列化漏洞深度分析
发布日期:2021-06-24 11:26:00 浏览次数:4 分类:技术文章

本文共 7757 字,大约阅读时间需要 25 分钟。

c844320bfa46a708fef50a5754286538.png

前言

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)三个方法,我们进行一个测试

0f9f413bdc21a8c6012a8a50fea4c006.png

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

public String t1private int t2private Boolean t3private Properties t4private Properties t5setter有有无无有getter有有有有有

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

9d68c36b878dfa7a501703e635ad2ac9.png

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

14c39eaf3b842fc0e58e8c74ad96f07f.png

结果:

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

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

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

c4a75c228b9ba08e788721a5642d1dcd.png

结果:

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

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

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

904e5dffe8a04f2720922594cc23cf36.png

结果:

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

a56943dafcae0000924abd8742b2787f.png

此时的调用链如下图

f588a5846a842a6228cb3ce677bfebbb.png

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

d97f9820fad305a71c68a4b550690dba.png

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

7d0a6cfccc0dd7533e089bb60865f1d5.png

此时的调用链如下图

39ae3a17c940f849c08c5593f087b615.png

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

935d8ae13c8638b86cb3c51c374ded19.png

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

6ccead937d660a81a5fd47bf32da3f04.png

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

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

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

ae99c170dde24a7bfc9966e4b0387788.png

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

7f9670874b53aee9704a8fe20e4ac402.png

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

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

31d0b52060e8c6af918f2fe3e982e17e.png

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

ceca4e25f23c0320fce337418f1e1f9a.png

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

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

7d61708a5ab0bec9571b3082117903f7.png

条件二、不是静态方法

929d6dfa4c4db1e2e38d7f555e478105.png

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

f0c6db1f42070fc82770c5dab0d6ee0d.png

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

dd072be55247770c550b02cd4897ca55.png

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

ef312ba895cc99dc188b87400919c774.png

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

f7cb74829fef6044db98403dd2c0f853.png

问题三解答

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

0c1c9e9d22a856e3b3891d48b86dabad.png

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

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

66c3b120a9a436a50af42de5366eb2d1.png

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

639c6ee7849b3704ff313904aa7635a7.png

完整的调用链如下

3063b9e191a63020dc4f0afc880b6d0e.png

总结:

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

parse(String text)、parseObject(String text)与parseObject(String text, Class clazz)目标类SetterGetter调用情况

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方法

85415b62972a2956e50442b5f509d8b5.png

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

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

_class与_transletIndex值皆由451行处defineTransletClasses()方法中获取

f3a0729e0fcf10baa6fafbaf17b9eb25.png

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

f6cd535a785be1be14e4422815b43593.png

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

6518b3383540c3660fa44b2663f772d9.png

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

95e764422c70a0719a45884f9e14addd.png

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

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

4784786ca253b22ec2ed13f9683fec85.png

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

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

0117be355c7001b0811192105be43ae7.png

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

d64229a1d0ee9e499264b287ee5bdb97.png

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

回头分析下上述流程

98ace7f8617d8788ebe317802eb7143d.png

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

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

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

现在回到getTransletInstance()方法中

441003d5cd3ded67865b2df147db7100.png

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

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

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

079f846b8725d88e293eb1bf2b87f776.png

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

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

我们继续向上跟踪代码

08c8d988d243621343bd0174430d59d9.png

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

继续向上跟踪

7775d47a759ac97bc7e57f417b06bba7.png

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

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

ae61052df9949971dc95e87a1b2f6e22.png

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

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

4cc9cbf3d8e39324cd17933d752e3632.png

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

转载地址:https://blog.csdn.net/weixin_32497403/article/details/113074869 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:onmessage websocket 收不到信息_WebSocket断开重连解决方案,心跳重连实践
下一篇:vs格式化json 不生效_vs code 格式化 json 配置

发表评论

最新留言

初次前来,多多关照!
[***.217.46.12]2024年04月01日 21时47分26秒