从WebLogic看反序列化漏洞的利⽤与防御
0x00 前⾔
上周出的 WebLogic 反序列漏洞,跟进分析的时候发现涉及到不少 Java 反序列化的知识,然后借这个机会把⼀些 Java 反序列化漏洞的利⽤与防御需要的知识点重新捋⼀遍,做了⼀些测试和调试后写成这份报告。⽂中若有错漏之处,欢迎指出。
0x01 Java 反序列化时序
Java 反序列化时序对于理解 Java 反序列化的利⽤或是防御都是必要的,例如有些 Gadget 为什么从 readObject ⽅法开始进⾏构造,为什么反序列化防御代码写在 resolveClass ⽅法中等。先写下三个相关的⽅法。
1.1 readObject
这个⽅法⽤于读取对象,这⾥要说的 readObject 跟很多同名的这个⽅法完全不是⼀回事的,注意下图中的⽅法描述符跟其它同名⽅法的区别。
java.io.ObjectInputStream 类的注释中有提到,要是想在序列化或者反序列化的过程中做些别的操作可以通过在类中实现这三个⽅法来实现。⽐如类 EvilObj 实现了这⾥的 readObject ⽅法(⽅法的描述符需
要跟注释提到的⼀样)的话,在类 EvilObj 的反序列化过程就会调⽤到这个 readObject ⽅法,代码例⼦:
construct用法其调⽤栈如下
看下 readSerialData ⽅法,在读取序列化数据的时候做判断若是该类实现了 readObject ⽅法,则通过反射对该⽅法进⾏调⽤。
到这⾥就能明⽩为什么有些 Java 反序列化利⽤的构造是从这⾥ readObject ⽅法开始的,然后通过 readObject 中的代码⼀步⼀步去构造最终达成利⽤,这次的 CVE-2018-3191 就是很好的⼀个例⼦,后⽂会讲到 CVE-2018-3191 使⽤的 Gadget。当然这只是 Java 反序列化利⽤构造的其中⼀种⽅法,更多的可以参考 ysoserial ⾥的各种 Gadget 的构造。
1.2 resolveClass 和 resolveProxyClass
这两个⽅法都是在类 java.io.ObjectInputStream 中,resolveClass ⽤于根据类描述符返回相应的类,resolveProxyClass ⽤于返回实现了代理类描述符中所有接⼝的代理类。这两个类的功能使得它们可以被⽤于 Java 反序列的防御,⽐如在 resolveClass ⽅法中可以先对类名进⾏检测然后决定是否还要继续进⾏反序列化操作。如果想要在这两个⽅法中添加⼀些操作(⽐如前⾯提到的做反序列化防御),那处理数据流的类需要继承 java.io.ObjectInputStream ,然后重写下⾯对应的⽅法:
protected Class<?> resolveClass(ObjectStreamClass desc)
protected Class<?> resolveProxyClass(String[] interfaces)
这⾥需要避免混淆的⼀点是这两个⽅法是在处理数据流的类中重写,⽽不是在被反序列化的类中重写,代码例⼦:
其调⽤栈如下
同理 resolveProxyClass 的重写⽅式也是这样。这⾥要知道的⼀点是并⾮在 Java 的反序列化中都需要调⽤到这两个⽅法,看下调⽤栈前⾯的 readObject0 ⽅法中的部分代码:
看 switch 代码块,假如序列化的是⼀个 String 对象,往⾥跟进去是⽤不到 resolveClass 或 resolveProxyClass ⽅法的。resolveProxyClass ⽅法也只是在反序列化代理对象时才会被调⽤。通过查看序列化数据结构⾮常有助于理解反序列化的整个流程,推荐⼀个⽤于查看序列化数据结构的⼯具:
1.3 反序列化时序
贴⼀张廖新喜师傅在“JSON反序列化之殇”议题中的反序列化利⽤时序图,⽤于从整体上看反序列化的流程。
普通对象和代理对象的反序列化⾛的流程是不⼀样的,可以看 readClassDesc ⽅法:
1.4 ⼩结
这⼀章主要是介绍了 Java 反序列化相关的三个⽅法,通过代码跟踪调试的⽅式来确定其在什么时候会被调⽤到,再结合反序列化的时序图就可以对反序列化的整个流程有⼀定的了解。其实去分析了下反序列化的时序主要是为了知道两点,第⼀个是反序列化的⼤体流程,第⼆个
是有哪些⽅法在这流程中有被调⽤到,为了解 Java 反序列化的利⽤和防御做⼀些知识准备。