前言
这是一篇炒冷饭性质的文章,距离FastJson的反序列化漏洞曝出已经过去了差不多两年的时间,网上相关的漏洞分析文章也是十分充足,但是我最近在阅读相关文章的时候发现普遍都是分析漏洞利用环节,许多解析部分的细节便不可得知。于是用了些时间分析了下其解析流程,也就有了本文。本篇内容主要为FastJson反序列化解析流程中涉及到的类及函数的分析。
整体调用栈
解析过程中的关键类
在用以调试的demo中,处理JSON数据的语句为:
1 | JSONObject obj = JSON.parseObject(data,Feature.SupportNonPublicField); |
整个JSON数据的处理也就是从parseObject
这个函数开始。
com.alibaba.fastjson.parser.DefaultJSONParser
从demo开始,进入的第一个类本应是JSON
,但是因为JSON类并未进行关键操作,因此就采取忽略,转而关注在com.alibaba.fastjson.JSON:74
处调用的DefaultJSONParser。
在DefaultJSONParser中,我们需要关注的函数有两个:
- parse
- parseObject
借助这两个函数,DefaultJSONParser
完成了对于JSON数据的解析。
com.alibaba.fastjson.parser.deserializer.FieldDeserializer
在Java反射中,可以通过Field获取某个类的属性或该属性的值,类FieldDeserializer
用于存储字段所对应的反序列化器,FiledDeserializer会存储至ObjectDeserializer中,其中Key对应为字段名称。
在FieldDeserializer
类中,主要由几类函数组成:
- 构造函数
- parseField
- setValue
- getFastMatchToken
com.alibaba.fastjson.parser.JavaBeanDeserializer
JavaBeanDeserializer
是一个反序列化器,也就是我们上面在FieldDeserializer
中提到的"反序列器"。
JavaBeanDeserializer
在进行反序列化时,会接受如下参数:
1 | protected <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName, Object object, int features) |
解析过程中的关键函数
com.alibaba.fastjson.parser.DefaultJSONParser$parseObject
在此函数中,DefaultJSONParser
会完成对于JSON数据的处理,我们所追求的命令执行也是在这一步中实现。该函数的主体实现包裹在一个while(True)
循环中,在这个while循环中,会不断取JSON数据中的下一个token(也就是字符)进行处理:
1 | while(true) { |
以下面这个demo为例子:
1 | {"name":{"@type":"..."}} |
会首先取出name
添加至SymbolTable
,再深入对name
键对应的数据进行循环处理。
com.alibaba.fastjson.parser.JSONLexerBase$scanSymbol
scanSymbol
主要调用点在刚才提到的parseObject中:
1 | key = lexer.scanSymbol(this.symbolTable, '"'); |
在scanSymbol
函数中,会首先取出键,比如下面这个例子中的name
:
1 | {"name":"..."} |
在取出键之后,会调用addSymbol
函数,将name
这个键名添加至SymbolTable
中:
com.alibaba.fastjson.parser.ParserConfig$deserialze
谈及该函数,必然需要理解ParserConfig
类,ParserConfig
在反序列化的过程维护了常用类型和反序列化器的对应关系,并将该对应关系存放至IdentityHashMap
。我们可以通过getDeserializer
方法获得对象反序列化器ObjectDeserializer
。
当出现未定义的类型时,则会在获取该类型的反序列化器的同时建立起对应关系,并存放至IdentityHashMap
。为了更好地理解它,可以举一个例子:ParserConfig$getDeserializer(Type type)
,我们可以分析其实现源码:
1 | ObjectDeserializer derializer = (ObjectDeserializer)this.derializers.get(type); |
该函数为获取ParserConfig中存储的反序列化器,当反序列化器存在时,则直接将其返回,不存在时则调用getDeserializer(Class<?> clazz, Type type)
,获取类与反序列器的对应关系,并在最后存入IdentityHashMap
:
1 | this.putDeserializer((Type)type, (ObjectDeserializer)derializer); |
parseField
由于该函数存在重名实现,因此以序号分开。
- com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer$parseField
- com.alibaba.fastjson.parser.deserializer.DefaultFieldDeserializer$parseField
虽然上述的两个函数重名,然而在一般情况下,DefaultFieldDeserializer$parseField
在JavaBeanDeserializer$parseField
中被调用:
1 | else { |
在JavaBeanDeserializer$parseField
中,当检测到存在与传入的Key所匹配的反序列化器时,便会调用DefaultFieldDeserializer$parseField
进行反序列化。
当进入DefaultFieldDeserializer$parseField
时,我们可以看到this
中已经存在fieldValueDeserializer
了:
随后调用对应的反序列化器,这里以我图中为例:
对应的语句为:
1 | value = this.fieldValueDeserilizer.deserialze(parser, fieldType, this.fieldInfo.name); |
com.alibaba.fastjson.parser.deserializer.FieldDeserializer$setValue
setValue
接受两个传参:
1 | public void setValue(Object object, Object value) |
object
为类对象,value
为属性值。
在setValue
中会进行字段的赋值操作,对object的get方法进行触发,当检测到method返回的类型与Map类型一致时,则会进一步触发反射,完成RCE,关键代码如下所示:
1 | else if (Map.class.isAssignableFrom(method.getReturnType())) { |
其中,method
为:
com.alibaba.fastjson.util.JavaBeanInfo.JavaBeanInfo$build
build
函数接受三个参数传入:
1 | public static JavaBeanInfo build(Class<?> clazz, Type type, PropertyNamingStrategy propertyNamingStrategy) |
在build
函数中,通过反射获得传入参数的clazz
类及父类的所有方法,随后遍历这些方法,将其中的set方法以及get方法取出,如图中fields
所示:
其中,beanInfo
的生成的语句为:
1 | beanInfo = JavaBeanInfo.build(clazz, type, this.propertyNamingStrategy); |