背景
通常在渗透的过程中会遇到很多 Weblogic 服务器,但通过 IIOP 协议进行发送序列化恶意代码包时,会面临着无法建立通讯请求发送恶意序列化失败。最近参与了一场在成都举办的《FreeTalk 2020 成都站》,有幸分享了关于 Weblogic IIOP 协议 NAT 绕过的几种方式。
PPT 下载地址:https://img.zhiiyun.com/Weblogic_IIOP_NAT.pptx
成果演示
Goby 工具中关于 Weblogic 基本都是用了 IIOP 协议绕过的方案,比较有代表性的漏洞为 CVE-2020-2551 漏洞。
![3151aef133d4b7843437caabb4fe5c3a.gif](https://img-blog.csdnimg.cn/img_convert/3151aef133d4b7843437caabb4fe5c3a.gif)
内部工具 weblogic-framework
使用了多项核心技术来进行优雅的测试 Weblogic,其中也使用了 IIOP 协议的绕过方案。
![7b0665897fa5ca1d5b8f905adcb9f27d.gif](https://img-blog.csdnimg.cn/img_convert/7b0665897fa5ca1d5b8f905adcb9f27d.gif)
协议
在开始之前,非常有比较提及一下以下这些协议相关的内容:
RMI:远程方法调用,本质上是 RPC 服务的 JAVA 实现,底层实现是 JRMP 协议,主要场景是分布式系统。
CORBA:跨语言(C ++、Java等)的通信体系结构,通常在 IIOP 协议中使用。
GIOP:主要提供标准的传输语法以及 ORB 通信的信息格式标准。
IIOP:CORBA 对象之间交流的协议,GIOP 的实现。
RMI-IIOP:解决 RMI 和 CORBA/IIOP 无法同时使用的技术方案。
Weblogic IIOP:Weblogic 自实现的 RMI-IIOP。
T3:WebLogic Server 中的 RMI 通信使用 T3 协议传输 WebLogic Server 和其他 Java 程序,包括客户端及其他 WebLogic Server 实例之间 数据。
T3 协议本质上 RMI 中传输数据使用的协议,但通过上面我们看到 RMI-IIOP 是可以兼容 RMI 和 IIOP 的,所以在 Weblogic 中只要可以通过 T3 序列化恶意代码的都可以通过 IIOP 协议进行序列化,正是因为这种情况,我进入 Weblogic 第三季度深度贡献者名单。
![3924d1bbf46d4474c80776e48da5815b.png](https://img-blog.csdnimg.cn/img_convert/3924d1bbf46d4474c80776e48da5815b.png)
流程
IIOP 序列化攻击流程
![24245743d5d230472365e9ccdabaf3f4.png](https://img-blog.csdnimg.cn/img_convert/24245743d5d230472365e9ccdabaf3f4.png)
一般 IIOP 序列化攻击的大致流程主要为首先构建恶意序列化代码,然后初始化上下文实例,最后通过 bind/rebind
进行发送恶意序列化代码,下图为关键代码。
![9d1aae8acff8fb13c2c77c8275774d9d.png](https://img-blog.csdnimg.cn/img_convert/9d1aae8acff8fb13c2c77c8275774d9d.png)
IIOP 初始化上下文流程
初始化上下文通过攻击流程中的 new InitialContext(env)
进行构建,最终的入口点通过 getInitialContext
方法进行构建,最终是进行流入到 InitialContextFactoryImpl.getInitialContext
进行初始化上下文。
![801e5cefeeb8302e36858fc72a364b9b.png](https://img-blog.csdnimg.cn/img_convert/801e5cefeeb8302e36858fc72a364b9b.png)
在流入 InitialContextFactoryImpl.getInitialContext
之后会通过 obj = ORBHelper.getORBHelper().getORBReference
来进行获取 NameService
,然后将获取到的 NameService
进行实例化创建上下文实例,提供后续的执行操作。
![2cd43607359de5462dc6b3ec6b35ff0d.png](https://img-blog.csdnimg.cn/img_convert/2cd43607359de5462dc6b3ec6b35ff0d.png)
rebind 流程
执行 rebind
流程中,首先会通过 this.getContext
方法进行获取前面所讲的上下对象,然后通过 rebind_any
进行发发送序列化代码,当前在此之前已经通过经过序列化的了。
![6e160d4b825cb1b1e4bb9c6d77f0c93f.png](https://img-blog.csdnimg.cn/img_convert/6e160d4b825cb1b1e4bb9c6d77f0c93f.png)
在 rebind_any
中,首先会通过 this._request
进行发送 rebind_any
建立 Socket 通讯,最后通过 this._invoke
方法进行执行最终的操作发送序列化代码。
![95b50281a49e5932e398dd7057ac9099.png](https://img-blog.csdnimg.cn/img_convert/95b50281a49e5932e398dd7057ac9099.png)
大致流程
所以最终大致的执行流程是如下图,获取 NameService
,基于获取的信息进行创建上下文实例(获取实际连接信息),然后进行发起 request
请求,最后进行执行 rebind_any
操作。
![9580d7e5aae3debd1136c1b606374c0b.png](https://img-blog.csdnimg.cn/img_convert/9580d7e5aae3debd1136c1b606374c0b.png)
环境准备
- Weblogic:12.1.3.0
- 协议:IIOP
- 漏洞编号:CVE-2020-2555
- 内网(Windows):http://10.10.10.173:7001
- NAT 网络( vulfocus ) :http://118.193.36.37:32769/ 内部 IP:172.17.0.5
NAT 网络构建通过 http://vulfocus.fofa.so/
进行搭建构造。
![841d0bb8d61b18e0938b08a9eff8518d.png](https://img-blog.csdnimg.cn/img_convert/841d0bb8d61b18e0938b08a9eff8518d.png)
成因
在后续的调试以及研究中我们所使用的版本为 12.1.3.0 版本,漏洞为 CVE-2020-2555 漏洞,以下为漏洞 POC,以及漏洞执行链,在这里不多讲该漏洞,有兴趣的可以移步漫谈 Weblogic CVE-2020-2555。
![46f3caaea5f9005211f876a766682770.png](https://img-blog.csdnimg.cn/img_convert/46f3caaea5f9005211f876a766682770.png)
执行链:
ObjectInputStream.readObject()
BadAttributeValueExpException.readObject()
LimitFilter.toString()
ChainedExtractor.extract()
ReflectionExtractor.extract()
Method.invoke()
Class.getMethod()
ReflectionExtractor.extract()
Method.invoke()
Runtime.getRuntime()
ReflectionExtractor.extract()
Method.invoke()
Runtime.exec()
当我们可以与 Weblogic 所处同一网段并且可达的时,可以看到是成功执行系统命令弹出计算器。
![ff193bb778ba6e496b18b1dbaf314928.gif](https://img-blog.csdnimg.cn/img_convert/ff193bb778ba6e496b18b1dbaf314928.gif)
通过 Wireshark 进行抓包可以,可以看到一共通讯了 2 次,第一次发送 LocateRequest
类型的 LocateRequest
的通讯操作获取 NameService
,第二次发送 Request
类型的 rebind_any
操作进行发送序列化代码。
![48e3ced72530d4c245f0fd9448589109.png](https://img-blog.csdnimg.cn/img_convert/48e3ced72530d4c245f0fd9448589109.png)
而进行测试公网中的靶机时抛出 Operation time out
异常,具体信息如下图。
![80d224e9e88c1e16483856f349d5a42e.png](https://img-blog.csdnimg.cn/img_convert/80d224e9e88c1e16483856f349d5a42e.png)
而在 Wireshark 中可以看到,与第一次获取NameService
中的内网 IP、端口进行了 Socket 通信。
![8ad303265a724e5dbc4e41167da5ca00.png](https://img-blog.csdnimg.cn/img_convert/8ad303265a724e5dbc4e41167da5ca00.png)
而在执行的流程中停留在了 createEndPoint
方法中,所以通信问题大概率是在此方法中引发的。
![ae6a007ac074687049b11132531d5080.png](https://img-blog.csdnimg.cn/img_convert/ae6a007ac074687049b11132531d5080.png)
在 createEndPoint
方法中,最后通过 MuxableSocketIIOP.createConnection
方法进行建立 Socket 通信,此时的通信变为了 Weblogic 运行的内网 IP 和端口。
![93f760f771a82109a9374b47dab20a70.png](https://img-blog.csdnimg.cn/img_convert/93f760f771a82109a9374b47dab20a70.png)
所以大体的情况为如下图,问题出现在发起 request
时调用的 createEndPoint
方法中,由于 createEndPoint
无法正常建立 Socket 通信导致后续的操作无法正常秩序。
![092192917c9de07f5de0b092648bb214.png](https://img-blog.csdnimg.cn/img_convert/092192917c9de07f5de0b092648bb214.png)
其实,我们也可以在 Weblogic 启动日志中也可以看到 Weblogic 关于端口和协议分配的情况,基本分配都是内网网卡的IP和端口同时会进行监听 0.0.0.0:7001
来处理协议的请求操作,那么现在问题来了,公网中的 Weblogic 服务器 99% 分配的都是内网 IP 和端口。
IIOP NAT 绕过方案
由于问题发生在响应的 Weblogic 在获取 NameService 时,响应的 IP和端口为内网中的端口,导致在后续 createEndPoint
建立 Socket 通信,所以我们可以进行在建立 Socket 通信之前修改为正确的 IP和端口(公网中的IP和端口)。
![565ea1c3b0890ecd62e5c0627fe70bb4.png](https://img-blog.csdnimg.cn/img_convert/565ea1c3b0890ecd62e5c0627fe70bb4.png)
GIOP 协议极简实现
当我们与服务器所处同一网段时,可以看到一共通讯了 2 次,第一次发送 LocateRequest
类型的 LocateRequest
的通讯操作获取 NameService
,第二次发送 Request
类型的 rebind_any
操作进行发送序列化代码。
![cdc88a8af39054b316d101a3cb41ec20.png](https://img-blog.csdnimg.cn/img_convert/cdc88a8af39054b316d101a3cb41ec20.png)
所以我们根据 Wireshark 中的信息,可以进行构建极简的 GIOP 实现,大体如下:
- 请求 LocateRequest,获取 NameService 以及获取 key
- 请求 Request,执行 rebind_any 操作,发送序列化代码
GIOP 协议
GIOP 协议大致由 Header 和 Message Type 进行构成,在 Header 包含了 Magic、Version、Message Type、Message Size。
![4174c50beeac69d79c186f77a67f1956.png](https://img-blog.csdnimg.cn/img_convert/4174c50beeac69d79c186f77a67f1956.png)
Message Type 的类型如下:
消息类型 | 始发方 |
---|---|
Request | Client |
Request | Server |
CancelRequest | Client |
LocateRequest | Client |
LocateReply | Server |
CloseConnection | Server |
MessageError | Both |
Fragment | Both |
实现
![bd2d244458e2e4bdc0389d870854b56c.png](https://img-blog.csdnimg.cn/img_convert/bd2d244458e2e4bdc0389d870854b56c.png)
获取 NameService 请求代码实现:
![59388d00e095489e68804514d7060c5a.png](https://img-blog.csdnimg.cn/img_convert/59388d00e095489e68804514d7060c5a.png)
执行 rebind_any 操作代码实现:
![3831122f8d2ad4e621e8caeae079f0f4.png](https://img-blog.csdnimg.cn/img_convert/3831122f8d2ad4e621e8caeae079f0f4.png)
最终效果:
![4a0c2158f49bd261991fe467256bf30f.gif](https://img-blog.csdnimg.cn/img_convert/4a0c2158f49bd261991fe467256bf30f.gif)
Javassist 字节码库
Javassist (JAVA programming ASSISTant) 是在 Java 中编辑字节码的类库,它使 Java 程序能够在运行时定义一个新类,并在 JVM 加载时修改类文件。
- Javassist.CtClass 是类文件的抽象表示形式
- Javassist.CtMethod 是类方法的抽象表示形式
读取类
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("test.Rectangle");
创建类
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("Point");
继承
cc.setSuperclass(pool.get("test.Point"));
写入
cc.writeFile();
cc.toClass();
需要注意的是 toClass()
会把当前修改的 Class 加入到当前的 ClassLoader
中。
创建方法
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("Point");
CtMethod ctMethod = new CtMethod(CtClass.voidType, "printName", new CtClass[]{}, cc);
修改方法
CtMethod ctMethod = ctClass.getDeclaredMethod("hello");
ctMethod.setBody("System.out.println(\"set body\");");
ctMethod.insertBefore("System.out.println(\"set before\");");
ctMethod.insertAfter("System.out.println(\"set after\");");
通过 Javassist 进行实现时,可以通过修改建立 Socket 通信之前的方法,将 ip、端口替换为正常的 IP 和端口,在这里选取的是 newSocket
方法,在第一个参数为 host
,第二个参数为 port
。
![14af56060a998cf7e464f30eeff72e81.png](https://img-blog.csdnimg.cn/img_convert/14af56060a998cf7e464f30eeff72e81.png)
最终修改的如下图:
![2bd33a3d837dcda58ecf18e911002990.png](https://img-blog.csdnimg.cn/img_convert/2bd33a3d837dcda58ecf18e911002990.png)
实现
在实现的过程中,仅需要在执行到 newSocket
方法时,将连接到 IP 和端口设置为正确的 IP 和 端口,核心代码如下图:
![e9ea1c7863ea0abda8856de5e79e4bba.png](https://img-blog.csdnimg.cn/img_convert/e9ea1c7863ea0abda8856de5e79e4bba.png)
最终效果:
![b181b611f4c22c6ffcfba8182401638f.gif](https://img-blog.csdnimg.cn/img_convert/b181b611f4c22c6ffcfba8182401638f.gif)
源代码修改
![da444c6f752c069eadb7da6efdbf6ad3.png](https://img-blog.csdnimg.cn/img_convert/da444c6f752c069eadb7da6efdbf6ad3.png)
在执行的流程中最终执行到了 createEndPoint
方法中,从执行流程来看主要如下图所示。
![ff6963ec5989a4f1d3811e0d4d154d8b.png](https://img-blog.csdnimg.cn/img_convert/ff6963ec5989a4f1d3811e0d4d154d8b.png)
在执行 rebind
方法发送序列化代码时,可以看到在此时已经变成了 Weblogic 内网中运行的 IP 和 端口,直到程序执行到 createEndPoint
抛出异常。
![fcf19084cb166ce428c411fc2b9f4cdf.png](https://img-blog.csdnimg.cn/img_convert/fcf19084cb166ce428c411fc2b9f4cdf.png)
而执行到 getInvocationIOR
方法时,会调用 IIOPRemoteRef.locateIORForRequest
方法来进行寻找 IOR
,并且将寻找到的 IOR
设置为当作当前 IOR
进行返回提供使用。
![776b1a227cfdb05e4e618556def400e3.png](https://img-blog.csdnimg.cn/img_convert/776b1a227cfdb05e4e618556def400e3.png)
在进入 locateIORForRequest
方法之后会通过 EndPointManager.findOrCreateEndPoint
来进行寻找或创建结束切点,可以看到此时 IOR
的 host
、port
变成了内网中的 IP 和端口。
![30c0734cc1b914db776a61a35c3309f2.png](https://img-blog.csdnimg.cn/img_convert/30c0734cc1b914db776a61a35c3309f2.png)
在进入 EndPointManager.findOrCreateEndPoint
最终会执行到 createEndPoint
方法中来进行建立 Socket 通信,在这里由于是内网的 IP 和端口无法成功建立通信,导致后续的利用也无法继续进行。
![31fc5235d3af5e093b04c9df4b124daf.png](https://img-blog.csdnimg.cn/img_convert/31fc5235d3af5e093b04c9df4b124daf.png)
实现
大致的问题点已经确认的清楚的情况下,我们可以通过修改原始代码的方式来进行实现绕过,大体思路为:
- 修改
weblogic.corba.j2ee.naming.ContextImpl
类中的rebind
方法 - 修改
weblogic.iiop.IIOPRemoteRef
类中locateIORForRequest
方法的 ior 参数,确保正常调用findOrCreateEndPoint
创建结束切点
首先修改 weblogic.corba.j2ee.naming.ContextImpl
类中 rebind
方法将正确的连接 IP 和端口加入到系统环境变量中。
![187071c53e644c4ef0a8fcba21ee90c1.png](https://img-blog.csdnimg.cn/img_convert/187071c53e644c4ef0a8fcba21ee90c1.png)
最后在 locateIORForRequest
方法读取系统环境变量中正确的 IP 和端口并且修改 ior
变量中相关的连接信息。使之能够正常的执行 findOrCreatePoint
方法创建结束切点
![3f8ef94966a9233485f99a290550cc81.png](https://img-blog.csdnimg.cn/img_convert/3f8ef94966a9233485f99a290550cc81.png)
最终效果:
![37a7dc6d49eb6b865fd8cf21f3594762.gif](https://img-blog.csdnimg.cn/img_convert/37a7dc6d49eb6b865fd8cf21f3594762.gif)
IIOP NAT 绕过方案总结
- GIOP 协议极简实现
- 优点:原始 Socket 发包,效率较快
- 缺点:构造难度较高,需要掌握协议相关知识
- Javassist
- 优点:修改难度较低
- 缺点:程序使用完毕之后需要重启,如果使用动态加载等方案可能导致资源占用率过高
- 源代码修改
- 优点:原生代码兼容性较强
- 缺点:修改难度较大
参考
- https://blog.csdn.net/weixin_33913377/article/details/94134763
- https://docs.oracle.com/cd/E13211_01/wle/wle42/corba/giop.pdf
- https://www.slideserve.com/milek/13-giop-iiop-ior
- https://www.cnblogs.com/scy251147/p/11100961.html