fastjson1.2.24漏洞分析及源码分析

近期出现fastjson的远程代码执行漏洞,fastjson国内知名度很高的json库,趁这个机会学习下,与大家分享下漏洞原因,fastjson反序列化流程。
远程代码执行漏洞

反序列原理
1 通过入参类型,生成反序列化器。
2 反序列化器调用入参类型的无参构造函数,创建对象。

3 解析序列化字符串,取出Key,Value值,调用相应的setKey(Value)方法。
这个漏洞主要是fastjson的一个 autotype 功能。下面代码是一个简单例子。

public class MyTest4AutoType {
    public static String userStr2="{\"@type\":\"com.mogujie.zhaoyun.mytest.User\",\"age\":12,\"birthday\":1493295579866,\"name\":\"myname\"}";

    public static void main(String[] args){
        User user2 = (User) JSON.parse(userStr2);
        System.err.println(user2.getName());
    }
}


这面是fastjson的一个黑科技,json字符串里增加一个特殊K,V值。key 固定为@type,value为此json字符串要反序列化的类全路径。这样就能通过入参,控制json字符串反序列化生成的对象。
下面这块就是fastjson(1.2.24)的代码

                if (key == JSON.DEFAULT_TYPE_KEY && !lexer.isEnabled(Feature.DisableSpecialKeyDetect)) {
                    String typeName = lexer.scanSymbol(symbolTable, '"');
                    Class<?> clazz = TypeUtils.loadClass(typeName, config.getDefaultClassLoader());

                    if (clazz == null) {
                        object.put(JSON.DEFAULT_TYPE_KEY, typeName);
                        continue;
                    }



Class<?> clazz = TypeUtils.loadClass(typeName, config.getDefaultClassLoader());typeName就是入参的类的全路径
上面的方法就是获得这个自定义类,安全漏洞就在这里。原因就是,这个方法没做任何类名的安全校验,也没有白名单机制。
通过前面反序列化原理知道,应用会执行自定义类的set方法赋值。loadClass这个方法没做任何过滤,也就是说可以传入任何jvm加载过的类,并执行setKey方法,Key也是传入的,可变的。
剩下的就是寻找符合以下条件的类:
1,JVM加载过的类;
2,有非静态set方法;
3,入参只有一个。
假如寻找到赋值后改变的是类变量的Class,那就能肆意妄为了。
上面的demo稍加改动,就变成下面的了,虽然后抛出异常,但是ClassLoader.setDefaultAssertionStatus仍然后执行。

public class MyTest4AutoType {
        public static String userStr2="{\"@type\":\"com.alibaba.fastjson.util.ASMClassLoader\",\"defaultAssertionStatus\":true}";
    public static void main(String[] args){
        User user2 = (User) JSON.parse(userStr2);
        System.err.println(user2.getName());
    }
}


1.2.24之后的版本都修复了这个漏洞,如果代码不做任何修改,自定义类的反序列化都会失败。解决方案就是增加白名单配置,例如 ParserConfig.global.addAccept("com.alibaba"),所有以com.alibaba开始的类都能反序列化了。fastjson还有黑名单配置功能,例如 ParserConfig.global.addDeny("javax.swing"),所有javax.swing开始的类都不反序列化。无论黑名单还是白名单,都是全局配置,可以统一控制fastjson。

反序列化解析


反序列化核心方法:com.alibaba.fastjson.JSON#parseObject(java.lang.String, java.lang.reflect.Type, com.alibaba.fastjson.parser.ParserConfig, com.alibaba.fastjson.parser.deserializer.ParseProcess, int, com.alibaba.fastjson.parser.Feature...);我们以这个方法为入口开始观察反序列化过程。

 

1,DefaultJSONParser parser = new DefaultJSONParser(input, config,featureValues);

DefaultJSONParser 是反序列化的核心类,是外观模式,包括配置(ParserConfig)、反序列化实现(ObjectDeserializer)、词法解析(JSONLexer)等功能组合在一起,依赖关系可以看下上面的类图。

DefaultJSONParser通过json字符串(input),反序列化全局配置(config),反序列化实例特征配置(featureValues)。这个实例创建好后,反序列化的基础就准备好了。

ParserConfig 全局唯一的反序列化配置,包括反序列化黑名单(denyList),反序列化白名单(acceptList),是否启用asm(asmEnable),反序列化实现类缓存集合(deserializers),常见关键字缓存(SymbolTable)。这个对象是单例的,包括两大部分:系统默认和自定义。asmEnable就是系统默认,程序加载时候会判断系统是否符合启动ams条件,并赋值。acceptList是自定义的,可以根据不同的业务系统配置不同的白名单。

 

2,T value = (T) parser.parseObject(clazz, null);

这行是反序列化的执行方法,反序列化所有的代码都在这里,分析几个关键代码。

 

2-1,ObjectDeserializer derializer = config.getDeserializer(type);

这个方法是根据入参类型获得反序列化器,每一个基本类型都有一个对应的反序列化器,如NumberDeserializer等,这些都会初始化并储存到deserializers里。自定义类会利用asm或反射生成,并存储到deserializers里,第二次相同的类型反序列化就不会重复创建,直接从deserializers获得。这种设计有两个优势,一是空间换时间,所有反序列化器都缓存起来。二是每一种类型都对应一种反序列化器,代码清晰,优化更有针对性。

 

2-2,ASMDeserializerFactory.createJavaBeanDeserializer(ParserConfig config, JavaBeanInfo beanInfo);

这个方法就是根据入参类型利用asm生成反序列化器,作者直接将asm核心代码迁移到fastjon里,所以fastjson不需要额外依赖asm库。

    使用ASM条件:

  • 非Android系统
  • 该类及其除Object之外的所有父类为是public的
  • 泛型参数非空
  • 非asmFactory加载器之外的加载器加载的类
  • 非接口类
  • 类的setter函数不大于200
  • 类有默认构造函数
  • 类不能含有仅有getter的filed
  • 类不能含有非public的field
  • 类不能含有非静态的成员类
  • 类本身不是非静态的成员类
假如不满足以上条件,fastjson会使用反射生成反序列化器:JavaBeanDeserializer(ParserConfig config, Class<?> clazz, Type type),性能低。

猜你喜欢

转载自mwei83.iteye.com/blog/2371421
今日推荐