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),性能低。
2-3,derializer.deserialze(this, type, fieldName);
反序列化生成对象。json字符串解析器,包括  JSONLexer,JSONLexerBase,JSONReaderScanner,JSONScanner,JSONToken。这块是反序列化性能优化的重点,fastjson中token是用于标识当前在解析过程中解析到的对象的一个标记,具体的值参考 JSONToken。这块使用了基于预测的优化算法,这块就是利用JSON的标准结构猜测字符会是什么样,提高判断效率。如:key的token后最有可能是":",":"之后可能是value的token或为"{"的token或为"["的token等等,从而可以根据前一个token预判下一个token的可能,减少重复判断。

3,return (T) value; 

 

整个反序列化流程就结束了,大家有兴趣的可以自己看看,有很多细节值得大家发现。

 

序列化解析

序列化类图


 
 

反序列化核心方法:com.alibaba.fastjson.JSON#toJSONString(java.lang.Object, com.alibaba.fastjson.serializer.SerializeConfig, com.alibaba.fastjson.serializer.SerializeFilter[], java.lang.String, int, com.alibaba.fastjson.serializer.SerializerFeature...);我们以这个方法为入口开始观察序列化过程,这块因为很多和反序列化的处理方法,设计都是一样的,整体简单描述下。

1,SerializeWriter out = new SerializeWriter(null, defaultFeatures, features);

SerializeWriter继承Writer,能够完成高性能的字符串拼接,相比StringBuilder做了适合json字符串拼装的优化。out初始化包括:申请序列化字符串存储空间(buf),这个空间是申请后是不释放的,放在Threadlocal,避免重复申请带来的性能损耗。序列化特征配置features,包括默认的配置和实例自定义配置,如利用符号(')替代符号("),禁止循环引用,是否输出值为null的字段等,详细的可以看下com.alibaba.fastjson.serializer.SerializerFeature。这块fastjson是用按位或计算的。

2,JSONSerializer serializer = new JSONSerializer(out, config);

JSONSerializer是序列化的核心类,是外观模式,包括配置(SerializeConfig)、序列化实现(ObjectSerializer)、序列化输出(SerializeWriter)、过滤器(SerializeFilterable)等功能组合在一起,依赖关系可以看下上面的类图。 

SerializeConfig是全局唯一的,和ParserConfig功能结构基本一样。它继承自IdentityHashMap,IdentityHashMap是一个长度默认为1024的Hash桶,每个桶存放相同Hash的Entry(可看做链表节点,包含key、value、next指针、hash值)做成的单向链表,IdentityHashMap实现了HashMap的功能,但能避免HashMap并发时的死循环。  

serializers,存储的序列化器,设计和deserializers是一致的。

SerializeFilterable列表,可视为在生成json串的各阶段、各地方定制序列化,大致如下:

 

  • BeforeFilter :序列化时在最前面添加内容
  • AfterFilter :序列化时在最后面添加内容
  • PropertyFilter :根据PropertyName和PropertyValue来判断是否序列化
  • ValueFilter :修改Value
  • NameFilter :修改key
  • PropertyPreFilter :根据PropertyName判断是否序列化

SerialContex 序列化上下文,在引用或循环引用中使用,该值会放入references的Hash桶(IdentityHashMap)缓存。

3,serializer.write(object);

这行是序列化的执行方法,列出几个关键代码。

3-1,ObjectSerializer writer = getObjectWriter(clazz);

这个方法是根据入参类型获得序列化器,整体和反序列化器的生成一致,很多代码结构都是一样的。

3-2,writer.write(this, object, null, null, 0);

每个类型的序列化器都会实现com.alibaba.fastjson.serializer.ObjectSerializer#write。各自实现自己的拼装json字符串逻辑

4,out.toString();

返回json字符串。

 

 参照:http://www.csdn.net/article/2014-09-25/2821866

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值