版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://nosec.org/home/detail/2859.html
来源:奇安信
本文版权归原作者所有,如有侵权请联系我及时删除
前言
在研究WebLogic相关的漏洞的时候大家一定见过JNDI、RMI、JRMP、T3这些概念,简单的说,T3是WebLogic RMI调用时的通信协议,RMI又和JNDI有关系,JRMP是Java远程方法协议。我曾经很不清晰这些概念,甚至混淆。因此在我真正开始介绍T3协议及其反序列化漏洞之前,我会对这些概念进行一一介绍。
JNDI
JNDI(Java Naming and Directory Interface)
是Java提供的Java 命名和目录接口
。通过调用JNDI
的API
应用程序可以定位资源和其他程序对象。JNDI
是Java EE
的重要部分,需要注意的是它并不只是包含了DataSource(JDBC 数据源)
,JNDI
可访问的现有的目录及服务有:JDBC
、LDAP
、RMI
、DNS
、NIS
、CORBA
。
Naming Service 命名服务:
命名服务将名称和对象进行关联,提供通过名称找到对象的操作,例如:DNS系统将计算机名和IP地址进行关联、文件系统将文件名和文件句柄进行关联等等。
Directory Service 目录服务:
目录服务是命名服务的扩展,除了提供名称和对象的关联,还允许对象具有属性。目录服务中的对象称之为目录对象。目录服务提供创建、添加、删除目录对象以及修改目录对象属性等操作。
Reference 引用:
在一些命名服务系统中,系统并不是直接将对象存储在系统中,而是保持对象的引用。引用包含了如何访问实际对象的信息。
看一下常见的JNDI的例子:
1 | jdbc://<domain>:<port>rmi://<domain>:<port>ldap://<domain>:<port> |
JNDI的查找一般使用lookup()
方法如registry.lookup(name)
。
RMI
RMI(Remote Method Invocation)即远程方法调用。能够让在某个Java虚拟机上的对象像调用本地对象一样调用另一个Java虚拟机中的对象上的方法。它支持序列化的Java类的直接传输和分布垃圾收集。
Java RMI的默认基础通信协议为JRMP,但其也支持开发其他的协议用来优化RMI的传输,或者兼容非JVM,如WebLogic的T3和兼容CORBA的IIOP,其中T3协议为本文重点,后面会详细说。
举例说明:
假设A公司是某个行业的翘楚,开发了一系列行业上领先的软件。B公司想利用A公司的行业优势进行一些数据上的交换和处理。但A公司不可能把其全部软件都部署到B公司,也不能给B公司全部数据的访问权限。于是A公司在现有的软件结构体系不变的前提下开发了一些RMI方法。B公司调用A公司的RMI方法来实现对A公司数据的访问和操作,而所有数据和权限都在A公司的控制范围内,不用担心B公司窃取其数据或者商业机密。
这种设计和实现很像当今流行的Web API,只不过RMI只支持Java原生调用,程序员在写代码的时候和调用本地方法并无太大差别,也不用关心数据格式的转换和网络上的传输。类似的做法在ASP.NET中也有同样的实现叫WebServices。
RMI远程方法调用通常由以下几个部分组成:
- 客户端对象
- 服务端对象
- 客户端代理对象(stub)
- 服务端代理对象(skeleton)
下面来看一下最简单的Java RMI要如何实现:
首先创建服务端对象类,先创建一个接口继承java.rmi.Remote
:
1 | // IHello.java |
然后创建服务端对象类,实现这个接口:
1 | // Hello.java |
创建服务端远程对象骨架并绑定在JNDI Registry上:
1 | // Server.java |
RMI的服务端已经构建完成,继续关注客户端:
1 | // Client.java |
至此,简单的RMI服务和客户端已经构建完成,我们来看一下执行效果:
1 | rmiregistry & |
简单解释一下RMI的整个调用流程:
- 客户端通过客户端的Stub对象欲调用远程主机对象上的方法
- Stub代理客户端处理远程对象调用请求,并且序列化调用请求后发送网络传输
- 服务端远程调用Skeleton对象收到客户端发来的请求,代理服务端反序列化请求,传给服务端
- 服务端接收到请求,方法在服务端执行然后将返回的结果对象传给Skeleton对象
- Skeleton接收到结果对象,代理服务端将结果序列化,发送给客户端
- 客户端Stub对象拿到结果对象,代理客户端反序列化结果对象传给客户端
WebLogic RMI
WebLogic RMI和T3反序列化漏洞有很大关系,因为T3就是WebLogic RMI所使用的协议。网上关于漏洞的PoC很多,但是我们通过那些PoC只能看到它不正常(漏洞触发)的样子,却很少能看到它正常工作的样子。那么我们就从WebLogic RMI入手,一起看看它应该是什么样的。
WebLogic RMI就是WebLogic对Java RMI的实现,它和我刚才讲过的Java RMI大体一致,在功能和实现方式上稍有不同。我们来细数一下WebLogic RMI和Java RMI的不同之处。
- WebLogic RMI支持集群部署和负载均衡
因为WebLogic本身就是为分布式系统设计的,因此WebLogic RMI支持集群部署和负载均衡也不难理解了。 - WebLogic RMI的服务端会使用字节码生成(Hot Code Generation)功能生成代理对象
WebLogic的字节码生成功能会自动生成服务端的字节码到内存。不再生成Skeleton骨架对象,也不需要使用UnicastRemoteobject
对象。 - WebLogic RMI客户端使用动态代理
在WebLogic RMI 客户端中,字节码生成功能会自动为客户端生成代理对象,因此Stub
也不再需要。 - WebLogic RMI主要使用T3协议(还有基于CORBA的IIOP协议)进行客户端到服务端的数据传输
T3传输协议是WebLogic的自有协议,它有如下特点:
- 服务端可以持续追踪监控客户端是否存活(心跳机制),通常心跳的间隔为60秒,服务端在超过240秒未收到心跳即判定与客户端的连接丢失。
- 通过建立一次连接可以将全部数据包传输完成,优化了数据包大小和网络消耗。
下面我再简单的实现一下WebLogic RMI,实现依据Oracle的WebLogic 12.2.1的官方文档,但是官方文档有诸多错误,所以我下面的实现和官方文档不尽相同但保证可以运行起来。
首先依然是创建服务端对象类,先创建一个接口继承java.rmi.Remote
:
1 | // IHello.java |
创建服务端对象类,实现这个接口:
1 | // HelloImpl.java |
创建服务端远程对象,此时已不需要Skeleton
对象和UnicastRemoteobject
对象:
1 | // HelloImpl.java |
WebLogic RMI的服务端已经构建完成,客户端也不再需要Stub
对象:
1 | // HelloClient.java |
最后记得项目中引入wlthint3client.jar
这个jar包供客户端调用时可以找到weblogic.jndi.WLInitialContextFactory
。
来看一下WebLogic RMI的调用流程
前置知识讲完了,小结一下这些概念的关系,Java RMI即远程方法调用,默认使用JRMP协议通信。WebLogic RMI是WebLogic对Java RMI的实现,其使用T3或IIOP协议作为通信协议。无论是Java RMI还是WebLogic RMI,都需要使用JNDI去发现远端的RMI服务。
T3协议及反序列化漏洞
T3协议简介
WebLogic Server 中的 RMI 通信使用 T3 协议在 WebLogic Server 和其他 Java 程序(包括客户端及其他 WebLogic Server 实例)间传输数据。T3协议包括
- 请求包头
- 请求主体
因此,在T3数据包构造过程中,需要发送两部分的数据。
我们通过部署好的环境,以及现成的payload,去看看这个协议包情况
T3协议
1 | t3 12.2.1 |
可以看出这是它的请求头。本文测试时发送的T3协议头为
1 | 第一行为“t3”加weblogic客户端的版本号。 |
weblogic服务器的返回数据为
1 | 第一行为“HELO:”加weblogic服务器的版本号 |
weblogic客户端与服务器发送的数据均以“\n\n”结尾。
可能会问这个地方和其他地方的T3协议怎么不一样?因为我用的exp中,它是伪造自定义了请求包的。可参考文章。
也就是说,如何判断对方是否使用T3协议等等,可以对服务器进行发包,发送请求头,对方则会返回weblogic服务器版本
前人经验:使用t3 9.2.0nAS:255nHL:19nn字符串作为T3的协议头发送给weblogic9、weblogic10g、weblogic11g、weblogic12c均合法。
检测代码如下:
1 | import os |
T3攻击方式
- 第一种:将weblogic发送的java序列化数据的地2到第7部分的反序列化数据进行替换
- 第二种:将weblogic发送的JAVA序列化数据的第一部分与恶意的序列化数据进行拼接。也就是替换第一部分的数据
以上来自网上文献:http://drops.xmd5.com/static/drops/web-13470.html
T3反序列化漏洞
上面,我详细解释了WebLogic RMI的调用过程,我们初窥了一下T3协议。那么现在我们来仔细看一下刚才抓到的正常WebLogic RMI调用时T3协议握手后的第一个数据包,有几点值得注意的是:
- 我们发现每个数据包里不止包含一个序列化魔术头(0xac 0xed 0x00 0x05)
- 每个序列化数据包前面都有相同的二进制串(0xfe 0x01 0x00 0x00)
- 每个数据包上面都包含了一个T3协议头
- 仔细看协议头部分,我们又发现数据包的前4个字节正好对应着数据包长度
- 以及我们也能发现包长度后面的“01”代表请求,“02”代表返回
这些点说明了T3协议由协议头包裹,且数据包中包含多个序列化的对象。那么我们就可以尝试构造恶意对象并封装到数据包中重新发送了。流程如下:
替换序列化对象示意图如下:
剩下的事情就是找到合适的利用链了(通常也是最难的事)。