安全的反序列化
下面是一个常用的黑名单检测,利用resolveClass对反序列化的类进行过滤
1 | public class MyObjectInputStream extends ObjectInputStream { |
重写了resolveClass,在readNonProxyDesc里调用了resolveClass
大概的调用栈
在使用时我们也要保证,我们这个安全的ObjectInputStream也必须是反序列化的起始输入流,这样才能保证整个反序列化过程中的安全
1 | ObjectInputStream objectInputStream = new MyObjectInputStream(new FileInputStream("test.bin")); |
不安全的反序列化
由于默认的ObjectInputStream没有相应的安全处理机制,任何类都可以被反序列化,所以存在安全隐患
而只要让ObjectInputStream作为反序列化的起始输入流,那么这个反序列化流很大程度上是不安全的。
这个场景其实很多地方都是存在的,对于我们自定义的一个反序列化类来说,我们无法控制用户在反序列化的时候使用的是安全还是非安全的ObjectInputStream,所以我们想做的就是在我们自己这个类里面增加一些安全机制,对应的就是Fastjson和我们的CC3.2.2之后的版本,对于CC来说,他通过重写了readObject来让默认情况下InvokeTransform这种不能被反序列化,这个机制来说是很安全的,但是对于Fastjson来说,实现的稍微有一点缺陷。
对于Fastjson来说,他相当于在readObject里面定义了一个安全的反序列化流
他重写了resolveClass和resolveProxyClass
readOrdinaryObject->readClassDesc->readNonProxyDesc->resolveClass
->readProxyDesc->resolveProxyClass
这个反序列化流的目的就是来检测JSONObject里面map里面保存的Object的安全性(String以及其他基础类型有自己对应的处理逻辑,不会进入readOrdinaryObject进而走到resolveCLass的逻辑)
表明上看起来这个方案确实天衣无缝,但是因为TC_REFERENCE的存在导致存在被绕过的可能
如果此时map里面的value是一个引用类型
那么他会在readHandle里面进行处理,通过反序列化里的输入流,读出对应引用对象位于handles里的下标–passHandle,然后把对象直接取出来返回(这个过程中我没有找到可以Override的函数,所以我个人觉得无法处理这种引用类型)
利用
既然我们知道JSONObject这种是有缺陷的,那么怎么绕过呢
首先肯定要利用引用机制
1 | emplatesImpl templates = new TemplatesImpl(); |
直接这样肯定会被拦截,因为我们这样写他不是一个引用值,那么我们要保证templates要在JSONObject之前就已经被反序列化出来了,那么我们需要利用到其他类,比如说ArrayList
先看一下他的序列化逻辑,他是按照elementData的下标,按照顺序进行序列化处理的
那么反序列化的时候,他也会按照顺序取出来
所以只要我们保证templates在JSONObject之前被add进ArrayList即可
(后面我把JSONObject换成JSONArray了,看起来跟ArrayList更贴合一点,但是处理逻辑都是类似的)
1 | TemplatesImpl templates = new TemplatesImpl(); |
在JSONArray去处理templates的时候,直接从readHandle里读出了templates,进而绕过check
下面就是fastjson1全版本通用反序列化payload
1 | byte[] bytecode = Files.readAllBytes(Paths.get("/Users/xuemo/Desktop/java_project/debug/target/classes/Evil.class")); |
根因
总结一下,为什么会出现这种引用绕过呢,我觉得有两个原因
1.因为java反序列化是从ObjectInputStream开始的,我们如果自定义一个ObjectInputStream,并设置自定义的ObjectInputStream为反序列化的入口,那么我们是可以获取到全部反序列化的过程,但是如果我们只是一个实现了Serializable的类,相当于我们只是反序列化的一个中间环节,我们无法控制那些在我们之前被反序列化出来的对象
2.ObjectInputStream没有提供可以Override方法来处理TC_REFERENCE这类情况