序列化是干什么的?
序列化是指将对象转化为可传输格式的过程,是一种数据持久化的手段,经常应用于网络传输,RPC等场景中。不管是存储到硬盘还是网络传输都需要持久化的能力,所以大多数的商用编程语言都具备这样的能力。
在Java的机制中,String、enum或者实现了Serializable接口的类都可以通过Java的序列化机制序列化成符合编码的数据流,然后通过InputStream和OutputStream将内存中的类持久化到网络或者硬盘中。
Java中一些常见的序列化框架
- 1、kyro: 速度快,序列化后体积小,但是跨语言支持比较复杂。
- 2、Hessian: 默认支持跨语言,但是效率不高。
- 3、Protostuff: 速度快,需静态编译
- 4、Java: 使用方便,可以序列化所有类,但是速度慢,占空间
- 5、fastjson: 这个是最常用的,序列化成JSON格式,可视化较高,而且最有意思的是它的迭代过程,充斥着和黑客们的斗智斗勇,我们仔细说说这个fastjson
fastjson的那些事
fastjson并没有使用Java自带的序列化机制,而是自定义了一套机制,它是遍历出类中所有的getter方法进行的。
在使用fastjson序列化对象的时候,如果对象中存在接口或者抽象类的时候,会将子类型抹去,保留下来接口或者抽象类的类型,这样就会使反序列化时无法拿到原始类型。
AutoType引入,第一次黑客博弈
为了解决这个问题,fastjson引入了AutoType,也就是在序列化的时候把原始类型记录下来,格式大概如下
json
{"@type":"","userName":"wabc"}
如上所示,@type制定了原始类的全限定类名,那么这就可能会被利用,攻击者就可以伪造JSON,比如设置type为com.sun.rowset下面的"JdbcRowSetImpl",这个JdbcRowSetImpl类支持dataSourceName传入一个rmi(远程方法调用)的源,解析这个uri的时候就会产生远程调用,这就会产生非常严重的安全问题。 当然新版本中对JdbcRowSetImpl已经加入了黑名单。
在再起的fastjson版本中(v1.2.25之前),AutoType是默认开启的,也没有什么限制,但是从1.2.25版本以后,fastjson就默认关闭了autotype支持,加入了黑名单和白名单来防御autotype开启时的一些特殊情况。
但是黑客也不是开玩笑的,fastjson既然加了黑白名单的校验,那么他们就开始研究怎么绕过checkAutoType
AutoType升级,黑客再次博弈
fastjson1.2.41之前,在checkAutotype的代码中会对黑白名单中的类进行过滤,如果要反序列化的类不在黑白名单中,才会对目标类进行反序列化,但是在加载的过程中,fastjson有一段特殊处理,在JVM中类名的前面需要加一个标识符L代表这是一个类或接口"[ " 代表是一个数组。所以我们看到的类名是比如Lcom.lang.Thread。而黑白明白又是通过startWith进行检测的,代码如下
所以黑客在自己想要使用的类库前后加上L后面加上;就可以绕过黑白名单的检测了
再次升级,并没卵用
在fastjson1.2.42版本中,在进行黑白名单的检测时先判断目标嘞的类名前后是否是L和;,如果是就截取在进行黑白名单的检测,但是这个方式。。。黑客在发现这个规则之后,前面和后面加上两个LL和;;就绕过了检测了。。
接着斗法,两边都上脾气了
在fastjson1.2.43版本中,在黑白名单判断之前,增加的了一个是否LL开头的判断,如果以LL开头直接抛出异常,直接把路堵死了,短暂的修复的这个问题。黑客就发现绕不过去了,就换了一种,因为你开头的不止L还有**[** 啊,然后用**[** 走了一遍老路,v1.2.43以前所有版本GG了。
fastjson作者继续增加难度
在v.1.2.44版本中只要目标类以[ 开头或者以;结尾的直接抛出异常,然后在接下来的几个版本,黑客主要攻击方式就是绕过黑名单,fastjson也不断完善黑名单。
好景不长
在升级到v1.2.47的版本的时候,黑客再次找到了攻击的方式,而且这个方式只有在关闭autoType的时候才生效。为啥呢?在fastjson中有一个全局缓存,在类加载的时候,如果autoType没开启就先尝试从缓存中获取类,如果缓存中有就直接返回,然后就直接跳过了黑白名单的检测了。怎么做呢? 第一步,黑客想办法把一个类加载缓存中,这时候需要一个不在黑名单中的类,这个类就是java.lang.Class,它反序列化的时候,会去json中的val值然后加载val对应的类,比如下面的这个格式
json
{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"}
这就把这个类搞进缓存了,然后在传入这个类的时候就会从缓存中获取,然后进行攻击了,说明啥?说明黑客比我们使用者读源码读的厉害~~
于是在v1.2.48的时候fastjson处理Class类的地方,设置了不进入缓存,解决了这个问题,之后的几个版本黑客和fastjson作者不断的就黑名单进行试探周旋
黑客换路了,引发了全网疯传的漏洞
利用异常进行攻击,想不到吧,fastjson中如果@type指定类是Throwable的子类,那么对应反序列化的处理类就会用ThrowableDeserializer,这个反序列化的方法中,当有一个字段的key也是@type的时候,就会把value当做类名,然后进行一次checkAutoType的检测,并且在checkAutoType中,有这样的一个约定,如果指定了expectClass,那么就会通过校验。反序列化的时候执行里面的getter方法,恰好Exception类中都有一个getMessage方法,黑客只需要一个自定义的异常,重写getMessage就可以攻击了。
对待这个漏洞有两种方式解决,一种就是升级,在v1.2.69的版本中对于需要过滤掉的expectClass进行了修改,新增了4个新的类,同时将原来的的判断转为了hash的判断。另一种就是使用v1.2.68中的safeMode,配置这个时候@type不再生效。
有人会问为什么安全漏洞这么多,很多人还是选择用fastjson呢? 因为它确实很香,序列化的结果可视化程度很高,而且它避免反射,使用缓存,同时做了很多算法优化,很大程度的提高的序列化和反序列化的效率。