WebLogic反序列化之CVE-2015-4852、CVE-2016-0638、CVE-2016-3510
CVE-2015-4852
、CVE-2016-0638
、CVE-2016-3510
这3个CVE常常被拿到一起来讲,因为后2个CVE都是以CVE-2015-4852
利用链为基础建立的。
按照我对反序列化利用链的理解,包含2个部分:
- 承担恶意代码的荷载Class
- 启动荷载Class的
启动Class
或者启动机制
CVE-2015-4852
的荷载Class就是较为熟知的Apache CC利用链,关于CC利用链的分析文章很多,就不在这里写了。放一篇我关于CC1的文章ysoserial gadget Commons-Collections1保姆级分析,核心就是ChainedTransformer、ConstantTransformer、InvokerTransformer。
关于承担恶意代码的荷载Class就到此为止,而怎么启动荷载,就需要先了解WebLogic的T3协议。因为WebLogic通过读取数据包里T3协议格式的序列化数据然后将其反序列化得到Object。
T3协议是WebLogic RMI使用的协议,是JRMP的强化版。
T3协议
WebLogic RMI就是WebLogic对Java RMI的实现,在功能和实现方式上稍有不同。我们来细数一下WebLogic RMI和Java RMI的不同之处。
- WebLogic RMI支持集群部署和负载均衡。
因为WebLogic本身就是为分布式系统设计的,因此WebLogic RMI支持集群部署和负载均衡也不难理解了。
- WebLogic RMI的服务端会使用字节码生成
(Hot Code Generation)
功能生成代理对象。
WebLogic的字节码生成功能会自动生成服务端的字节码到内存。不再生成Skeleton骨架对象,也不需要使用UnicastRemoteObject
对象。
- WebLogic RMI客户端使用动态代理。
在WebLogic RMI客户端中,字节码生成功能会自动为客户端生成代理对象,因此Stub
也不再需要。
- WebLogic RMI主要使用T3协议(还有基于CORBA的IIOP协议)进行客户端到服务端的数据传输。
T3传输协议是WebLogic的自有协议,它有如下特点:
- 服务端可以持续追踪监控客户端是否存活(心跳机制),通常心跳的间隔为60秒,服务端在超过240秒未收到心跳即判定与客户端的连接丢失。
- 通过建立一次连接可以将全部数据包传输完成,优化了数据包大小和网络消耗。
关于T3协议我本来想找一下有没有详细的定义,但是百度Google都没有找到,直到看到SeeBug里有篇文章说因为WebLogic闭源的原因就没有T3协议规则这方面的资料。不过网上对应Poc利用部分的T3协议分析倒是有。
具体分析参考漏洞原理和weblogic t3 协议利用与防御
- 发现每个数据包里不止包含一个序列化魔术头。
(0xac 0xed 0x00 0x05)
- 每个序列化数据包前面都有相同的二进制串。
(0xfe 0x01 0x00 0x00)
- 每个数据包上面都包含了一个T3协议头。
- 仔细看协议头部分,发现数据包的前4个字节正好对应着数据包长度。
- 以及我们也能发现包长度后面的
01
代表请求,02
代表返回。
这些点说明了T3协议由协议头包裹,且数据包中包含多个序列化的对象。
现在知道了T3协议的格式,那么接下来有2种思路
-
替换数据包中多个序列化对象任意一个为恶意序列化数据。
-
weblogic发送的JAVA序列化数据的第一部分与恶意序列化数据进行拼接。
其实在我看来是一个意思,无非是选择替换第几个正常序列化部分。
网上的payload都是直接替换第1个序列化对象,我参考的Poc是这个Weblogic_direct_T3_Rces。
搞清楚怎么在T3协议数据流中加入恶意序列化数据后,接下来就可以说一说服务端是如何反序列化恶意数据并启动CC链。
从数据流到Object
Weblogic通过7001
端口,获取到流量中T3协议的java反序列化数据。
通过调用栈,可以发现Weblogic最后通过ObjectInputStream读入序列化数据,并在readClassDesc(boolean unshared)
方法中拿到类描述符来确定字节流中传递数据的类型,并交给对应的方法进行处理。
//ObjectInputStream.java
/**
* Reads in and returns (possibly null) class descriptor. Sets passHandle
* to class descriptor's assigned handle. If class descriptor cannot be
* resolved to a class in the local VM, a ClassNotFoundException is
* associated with the class descriptor's handle.
*/
private ObjectStreamClass readClassDesc(boolean unshared)
throws IOException
{
byte tc = bin.peekByte();
ObjectStreamClass descriptor;
switch (tc) {
case TC_NULL:
descriptor = (ObjectStreamClass) readNull();
break;
case TC_REFERENCE:
descriptor = (ObjectStreamClass) readHandle(unshared);
break;
case TC_PROXYCLASSDESC:
descriptor = readProxyDesc(unshared);
break;
case TC_CLASSDESC:
descriptor = readNonProxyDesc(unshared); //进这里
break;
default:
throw new StreamCorruptedException(
String.format("invalid type code: %02X", tc));
}
if (descriptor != null) {
validateDescriptor(descriptor);
}
return descriptor;
}
这里会走case TC_CLASSDES
,在readNonProxyDesc()
里通过readClassDescriptor()
拿到描述符并作为参数传给resolveClass(readDesc)
方法拿到相对应的Class对象,最后通过desc.initNonProxy()
初始化。
//ObjectInputStream.java
/**
* Reads in and returns class descriptor for a class that is not a dynamic
* proxy class. Sets passHandle to class descriptor's assigned handle. If
* class descriptor cannot be resolved to a class in the local VM, a
* ClassNotFoundException is associated with the descriptor's handle.
*/