1. 漏洞背景与影响范围
1.1 这个漏洞为什么值得拿出来单讲
先说一个判断:CVE-2019-2725 是整个 WebLogic 反序列化漏洞家族里非常特殊的一个,它不是简单的"补丁漏了",而是 Oracle 在修复上一个漏洞时留下的"绕过"产物。很多人只知道它是一个 RCE(远程命令执行),但没搞明白它跟前代漏洞(比如 CVE-2017-10271、CVE-2018-2628)的关系,导致修复时总是修了又破、破了又修,非常典型的"打地鼠"场景。
这个漏洞影响的是 Oracle WebLogic Server 的wls9-async 组件,涉及的版本主要是10.3.6.0和12.1.3.0。漏洞的本质是:WebLogic 在解析通过 HTTP 协议进来的 SOAP 请求时,会调用一个名为AsyncResponseService的异步服务,而该服务在反序列化 XML 中嵌入的 Java 对象时没有做足够的黑名单校验,攻击者可以构造一份畸形的 SOAP 报文,让服务器反序列化恶意对象,从而实现远程代码执行。
2019 年 4 月 Oracle 发布季度补丁时,官方只修补了 wls-wsat 组件的反序列化绕过问题,却漏掉了 wls9-async 这个入口,于是这个漏洞在补丁发布后没多久就被人在野利用。我印象里国内最早的一批利用是针对中间件服务器的挖矿程序,攻击者拿到权限后迅速下载木马、植入后门,整个过程只需要一个 HTTP POST 请求,快得让人反应不过来。
这篇文章适合三类人看:一是中间件管理员,需要确认自己的 WebLogic 是否受影响、怎么修;二是安全运维,需要写检测规则和加固清单;三是刚接触 Java 反序列化漏洞的初学者,想搞明白 WebLogic 这类反序列化漏洞到底是怎么一步步被利用的。
1.2 追溯根因:从 wls-wsat 到 wls9-async
要理解 CVE-2019-2725,得先从它的"前辈"说起。2017 年底曝光的 CVE-2017-10271 影响的是 wls-wsat 组件,Oracle 在 2018 年的补丁里给 wls-wsat 加了一个黑名单。这个黑名单的思路很直接:反序列化的时候,如果碰到黑名单里的类,就直接抛异常拒绝执行。
思路本身没问题,问题出在覆盖面上——补丁只补了 wls-wsat,wls9-async 这个同样存在反序列化逻辑的接口没有被覆盖到。wls9-async 是 WebLogic 用来处理异步消息通知的组件,通过/_async/AsyncResponseService这个路径对外提供服务,它同样基于 XML 解析 SOAP 消息,同样调用了 XMLDecoder 进行对象反序列化,只是入口不一样。
这就好比一栋楼有两个门,保安只守住了前门,后门不仅没锁,门上还贴着"贵重物品请放此处"的告示。攻击者只是把原本打在 wls-wsat 上的攻击载荷,原封不动地转移到了/_async/AsyncResponseService上,发现居然还能打通。CVE-2019-2725 的官方描述里也明确写了:"攻击者可以通过精心构造的 SOAP 请求,在未授权的情况下实现远程代码执行。" 所谓未授权,就是不需要任何账号密码,直接往 7001 端口发一个 POST 请求就行。
这里要特别提一下 XMLDecoder。这个类是 Java 自带的 XML 反序列化工具,它能把 XML 描述的对象图转换成 Java 对象。在设计之初它就不是给不可信输入用的,因为 XML 里可以声明任意类的实例化、字段赋值、方法调用,这等于给了攻击者一个"拿着 XML 就能写代码"的能力。CVE-2019-2725 的核心问题就在这里:WebLogic 把来自网络的不可信 XML 直接交给了 XMLDecoder,而且没有做严格的类过滤。
2. 漏洞原理剖析
2.1 反序列化攻击的基本逻辑,用生活化的方式讲
Java 反序列化攻击听起来玄乎,拆开看其实就是三步。
第一步,攻击者在提交的数据里嵌入一个 Java 对象的序列化表示。序列化就是把对象变成字节流,反序列化就是把字节流变回对象。问题在于,反序列化的过程中,对象的readObject方法会被调用,而某些类的readObject方法里,藏着"危险的后续动作"。
第二步,攻击者会去找一条"gadget chain"(利用链)。这条链子的意思是:从入口类出发,通过一层层的 getter、setter、方法调用,最终抵达一个能执行系统命令的终点类。举个不太严谨但容易理解的类比:你往一台自动售货机里投入一枚特制的假硬币(恶意数据),这枚硬币看起来正常,但在验币器里被弹来弹去的的时候,会恰好连续触发几个开关,最后激活了"掉饮料"的机械臂,只是掉出来的不是饮料,而是一台命令执行器。
第三步,把恶意数据塞到服务端会反序列化的位置,等服务器处理请求时自动触发。攻击者不需要交互,不需要提前知道服务器密码,只需要服务器存在这个漏洞接口,就能拿到执行权限。
CVE-2019-2725 利用的链子,在当时的公开分析中被认为是基于oracle.toplink.internal.sessions.UnitOfWorkChangeSet等类构造的。这些类在反序列化时,会触发对Method的调用,最终可以调用Runtime.exec这类执行命令的方法。我不在这里展开完整利用链的每一个类和方法,因为那是另一个大话题,而且网上已经有很多分析文章,我只想让你记住一个结论:这条链子是真实存在且可稳定触发的,从请求报文到命令执行,中间没有任何身份验证环节。
2.2 CVE-2019-2725 的触发链路拆解
这个漏洞的 HTTP 请求报文长什么样?大体上是这样一条链路:
POST /_async/AsyncResponseService HTTP/1.1 Host: target:7001 Content-Type: text/xml SOAPAction: (空) <soapenv:Envelope> <soapenv:Body> ...反序列化触发载荷... </soapenv:Body> </soapenv:Envelope>WebLogic 收到这个请求后,会交给 wls9-async 的AsyncResponseService处理。这个服务接收 SOAP 消息后,把 body 里的 XML 交给XMLDecoder完成反序列化。XMLDecoder 不像ObjectInputStream那样要求必须是严格序列化的字节流,它宽容得多,几乎整个 XML 都能变成它的操作指令。
这就是 2725 跟纯 JNDI 注入(比如 CVE-2021-2109 那类)不一样的地方——它走的是完全的 XMLDecoder 指令路线。攻击者在 XML 里声明一个<object class="java.lang.Runtime" method="getRuntime">,然后在这个对象上继续声明调用exec方法,就可以执行任意系统命令。当然实际利用的时候不会这么直白,因为 WebLogic 内部还有一些过滤逻辑,但总体思路如此。
值得注意的一个细节是:这个接口对 HTTP 方法并不挑剔,GET、POST 都能到处理逻辑,只是 POST 方便携带 XML body。我在复现环境里测试过,即使SOAPAction为空,或者 Content-Type 写不规范,服务也能正常触发。这意味着基于"请求头是否规整"来做拦截的思路,基本是白费功夫。
3. 自查与验证:怎么判断环境是否受影响
3.1 不依赖扫描器的快速排查三件套
很多人拿到这台 WebLogic 服务器的第一反应是"上去执行一个 POC",但我不建议在线上环境直接打恶意载荷。正确做法是先做静态排查,确认环境到底存不存在漏洞接口。
第一步,看版本。SSH 登录到 WebLogic 服务器,找到安装目录下的registry.xml或version.xml,查看版本号。如果版本在10.3.6.0或12.1.3.0范围内,且没有打上 2019 年 4 月之后的补丁,基本可以判定为高风险。注意,WebLogic 的补丁是累计型的,不是说"我 12.1.3.0 的补丁级别比较新就没事",要看具体的 PSU(Patch Set Update)版本号,以 Oracle CPU Quarterly 为准。
第二步,看部署的 war 包。wls9-async 是内置组件,对应的 web 应用路径是/_async。你可以在 WebLogic 控制台里看已部署的应用列表,也可以直接看domains/基础域名/servers/AdminServer/tmp/下的部署缓存,或者用下面的命令直接请求一下:
curl -i http://127.0.0.1:7001/_async/AsyncResponseService -X POST如果返回的状态码是 404 或 405,说明这个路径可能不存在或者已被禁用;如果返回 500 或者直接进入某些异常处理流程,说明这个组件是启用的,需要重点盯防。也可以检查_async目录下是否有AsyncResponseService对应的部署包wls9_async_response.war。
第三步,看网络层访问控制。用netstat -antp看一下 7001 端口监听在什么地址上。如果 WebLogic 直接暴露在公网或者对办公网大范围开放,那么漏洞的可达性就从"理论可利用"升级为"很容易被打"。我在真实处置中遇到的情况是:很多企业把 WebLogic 放在内网,但内网也是"大互通"状态,任何一台办公终端都能直接访问中间件的管理端口,这种边界形同虚设。
3.2 最小化验证思路,既不触发警报又能确认风险
如果你需要做一次验证,但又不希望真的让目标机器执行命令、留下痕迹,我的建议是用"无害探测 + 响应差异匹配"的方式。
具体来说就是:构造一个不含恶意代码的 XML 报文,但它的结构刚好能触发服务端解析逻辑。如果服务端进入反序列化的处理流程,在开启了 WebLogic Access Log 的条件下,请求日志里会出现/_async/AsyncResponseService的记录,并且请求可能返回500或者带异常堆栈信息;如果服务端在入口处就拦截了,返回的可能是404或者纯静态的 HTTP 错误页。
比如可以构造这样的请求(注意不含攻击载荷,只探测接口可达性):
POST /_async/AsyncResponseService HTTP/1.1 Host: 127.0.0.1:7001 Content-Type: text/xml Content-Length: 120 <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"> <soapenv:Body> <test></test> </soapenv:Body> </soapenv:Envelope>然后对比/wls-wsat/CoordinatorPortType这个同样容易出问题的路径的返回情况。如果_async返回了明显的 WebLogic 异常页面或 500 状态,而wls-wsat返回 404,说明你至少有一个入口是活着的。
我没有在这里给出完整的命令执行载荷,原因很简单:你不需要靠打一遍完整的攻击链来确认漏洞存在,探测接口可达性已经足够决策。线上滥用完整利用代码,很容易触发安全设备的告警,还会给攻击溯源留下自己的 IP,属于出力不讨好。
4. 修复与加固实操
4.1 官方补丁怎么打,以及打补丁的注意事项
CVE-2019-2725 的官方修复方案是 Oracle 2019 年 4 月 CPU 补丁,编号上对应的补丁集是28969423。如果你的 WebLogic 版本是 10.3.6.0,需要先升级到 10.3.6.0.190416 这个 PSU 级别;12.1.3.0 版本同理,需要 12.1.3.0.190416 及以上的补丁级别。
打补丁的标准流程不复杂,但有几个坑必须提醒:
第一个坑:补丁安装前置条件不满足。打补丁之前必须先确认操作系统的依赖库完整,特别是执行opatch时需要用到libclntsh.so这类共享库,同时确保ORACLE_HOME环境变量指向正确的 WebLogic 安装目录。我见过有人把环境变量指到数据库的 ORACLE_HOME,结果 opatch 直接把整个 WebLogic 目录搞乱的案例。
第二个坑:补丁后没有清理 tmp 缓存。WebLogic 会把部署的应用解压缓存到 domains 的servers/AdminServer/tmp/.internal/目录下,补丁更新了 jar 包,但缓存没刷新,重启后可能仍然调用旧的 class。正确做法是打补丁后,先停掉 AdminServer 和 ManagedServer,清空每个 Server 的 tmp 和 cache 目录,再启动服务。
第三个坑:补丁版本跟集群版本不一致。如果你的环境是 WebLogic 集群,建议所有节点一次性全部打上补丁,保持 patch level 一致。混跑的高低版本节点在启用集群内部通信时可能出现不兼容的通告,严重的时候会导致会话复制异常。
打补丁期间必须申请停机窗口。WebLogic 的补丁理论上支持滚动重启,但在 10.3.6 这种老版本上,我实测滚动重启的风险远高于一次性全部重启,除非是核心业务要求 7x24 小时不能间断,否则我不建议图省事搞滚动。
4.2 临时缓解措施:不重启也能降低风险
如果业务不允许马上打补丁,Oracle 官方和业界都有临时缓解方案。最有效的一条是禁用 wls9-async 组件的对外访问。
具体操作是在 WebLogic 控制台或者直接编辑web.xml,把AsyncResponseService对应的 servlet 映射注释掉。但控制台操作对权限有要求,而且动态生效需要重载应用;更干脆的办法是直接用防火墙或安全组规则,把/_async/和/wls-wsat/这两个路径的访问限定到可信来源。
如果你的 WebLogic 前面有 Nginx 或 Apache,可以做一层 URI 级别的过滤:
location ~ ^/_async/ { deny all; return 403; } location ~ ^/wls-wsat/ { deny all; return 403; }实测下来这个配置对阻断外部扫描是很有效的。但要注意,如果攻击者走的是 WebLogic 自身的 T3 协议或者内部转发,Nginx 这层就看不到了。所以这只是临时缓解,不是根治。
另一种缓解方案是删除部署目录下的wls9_async_response.war和wls-wsat.war的部署注册信息。但直接删 war 包可能导致 WebLogic 控制台出现部署错误,而且下次重启时如果Config.xml里还有部署项,可能报应用找不到的错。我建议删除之前先备份,并同步修改config.xml里的相关<app-deployment>条目。
4.3 深层加固:不只是补洞,还要防横向
修完漏洞不等于安全了,因为攻击者可能已经利用漏洞留下过后门。处置完补丁之后,下一步应该做工作组层面的排查。
第一件事,检查 WebLogic 进程是否被注入了恶意 jar。用jps或jstack看一下 JVM 进程,确认是否有异常的线程名。也可以去临时目录看看有没有可疑的.jar或.jsp文件,特别是名字跟正常业务完全不搭边的那种。
第二件事,检查系统层有没有反弹 shell 的痕迹。看看/tmp、/var/tmp下有没有可疑的脚本和二进制文件,crontab -l检查定时任务,/etc/ld.so.preload检查库劫持。
第三件事,保留 WebLogic access log。我见过不少企业默认把访问日志级别开得极低,出了问题连"攻击者从哪里来"都查不到。建议把访问日志打开,至少记录 POST body 的大小和来源 IP。日志的保存周期不要低于 6 个月,这是溯源的重要依据。
5. 常见问题排查实录
5.1 排查中的典型误区:为什么你测不到漏洞
我在帮助企业处置 2725 相关的安全事件时,经常遇到几种测不到漏洞的困惑。整理成速查表,方便你按图索骥。
| 现象 | 可能原因 | 进一步排查方式 |
|---|---|---|
请求/_async/AsyncResponseService返回 404 | 该路径被前置防火墙/Nginx 拦截,或应用未部署 | 直接在内网一台机器上用 IP+端口访问,绕过七层代理试试 |
| 返回 500 但日志里看不到异常堆栈 | 日志级别设置为 ERROR 以下,或者异常被统一处理 | 到servers/AdminServer/logs/下看*.log和*.out文件 |
| 探测 wls-wsat 正常,但 _async 无响应 | wls-wsat 和 wls9-async 是不同组件,可能分别打了不同补丁 | 分别测试两个路径,不能只测一个入口就判定安全 |
| 打了 4 月补丁后仍可访问 _async 路径 | 补丁只是加了类过滤,没有禁用接口 | 用包含恶意类的载荷测试是否被拦截,如果仍可执行说明补丁未生效 |
| WebLogic 是新装的 12.2.x,仍然中招 | 12.2.x 也受部分反序列化漏洞影响,且组件默认开启 | 不能只关注 2725 对应的旧版本,其他反序列化入口同样要排查 |
其中"返回 500 但日志无异常"这个现象最坑。因为 WebLogic 的高版本默认开启了一个"安全异常抑制"机制,部分反序列化异常被吞掉,只在更底层的日志里打印。你要看日志目录/diagnostic_images/下的*_DiagnosticImage.log,那里往往记录着被压制的异常详情。
5.2 补丁打不上:典型的 3 个卡点与解法
卡点一:opatch 报 "Oracle Home not known at /..."
这个错误八成是环境变量没配对。确认一下ORACLE_HOME和MIDDLEWARE_HOME是否指向 WebLogic 的wlserver_10.3或wlserver目录,不要指向整个Oracle/Middleware根目录。另外 opatch 工具的路径应该在$ORACLE_HOME/OPatch/opatch,不同版本之间不要混用。
卡点二:补丁应用到一半,报某个 jar 包 checksum 不匹配
典型的组件文件被动过,可能是之前打过别的补丁、也可能有人手工替换过 jar 包。这个没有捷径,需要对照官方补丁说明里的文件清单,用cksum检查每个 jar 包。如果实在找不到被改的文件,最稳妥的办法是把 WebLogic 停掉,备份原目录后重新解压一个干净的安装目录,再打补丁。
卡点三:10.3.6 打 4 月 CPU 补丁前,必须先打 1 月的补丁
Oracle 的季度补丁是叠加式的,如果你直接跳到 190416,opatch 会报"requires earlier PSU",这是正常的,不是失败了。你需要找到 190115(1 月 CPU)先装上,再上 190416。这一点官方文档里写得不太显眼,但实操中非常常见。
5.3 做完加固后,怎么验证确实安全了
打完补丁、配完过滤规则之后,最好做一轮"验收测试",确认修复确实生效。
我的习惯是准备一份不包含真实攻击载荷的"探针脚本",用 Python 的requests或curl发送特制报文到_async接口,观察响应变化。具体来说,补丁生效后,当报文里包含明显反序列化特征类名的 XML 结构时,服务端应返回异常或拒绝处理,而正常业务报文则不受影响。
另外别忘了做正向验证——用正常业务调用走一遍_async接口(如果你还需要这个接口的话),确认没有因为加固导致业务功能挂掉。有几次我只注意拦截,忘了业务方还在用这个接口做异步通知,加固一上,业务直接报错,运维半夜打电话找我,很尴尬。
注意:所有验证请求必须在企业授权的测试环境或系统管理员明确同意的环境下进行,避免在未授权系统上做任何探测。安全测试不是攻击授权,不是所有"试一下"都是合规的。
6. 最后再分享几个实操心得
CVE-2019-2725 这类 WebLogic 反序列化漏洞,给我最大的警醒不是某个组件写得不安全,而是补丁管理在真实运维里的滞后程度。我处理过不止一个案例,客户在漏洞曝光半年之后才知道自己中了招,期间整整半年服务器都在公网裸奔。问他们为什么没打补丁,答案几乎都是"担心影响业务,不敢动"。
这里我想说一个朴素的建议:对中间件类的漏洞,不要抱着"不被打就不修"的心态,尤其是 WebLogic、JBoss 这类常年在 RCE 漏洞榜单前排的软件,上线之初就应该把补丁更新列入例行运维计划,而不是等 Red Hat 的告警邮件堆了一堆才着急。
回到 CVE-2019-2725 本身,如果你今天看完这篇文章,能记住三件事就够了:第一,它的根因是 wls9-async 组件把不可信 XML 直接交给 XMLDecoder 处理,入口路径是/_async/AsyncResponseService;第二,修复的核心不是删接口,而是打上正确的补丁并确认补丁确实生效;第三,修复只是起点,攻击者可能早就在你还没打补丁的时候进来逛了一圈,所以事后检查系统后门跟打补丁同等重要。
我一直觉得,安全工作的本质是"把边界看住,把变更做稳,把痕迹留全"。这三个词听起来简单,但每一条背后都是无数个踩坑换来的。希望这篇文章能让你少踩几个坑。