浅谈 weblogic CVE-2020-2551 漏洞 & 外网POC构造

 

0x00 基础概念

学习这个漏洞需要一些前置知识,比如CORBA与RMI

简单的概述一下:

CORBA是OMG制定的一套技术标准,用于分布式应用,其中用到了IDL进行跨语言支持,客户端与服务端之间用IIOP协议进行通信

RMI是另一种分布式应用技术,在JAVA中可以用JNDI进行简化应用,客户端与服务端使用JRMP协议进行通信,不过在weblogic中RMI使用的是T3协议,关于这个之前也爆出过不少漏洞

RMI-IIOP结合了RMI与CORBA各自的优点,通过IIOP协议部署RMI应用

官方文档也提到:

RMI server objects can use the IIOP protocol and communicate with CORBA client objects written in any language

 

0x01 RMI-IIOP

暂时不提weblogic,先关注一下如何编写一个RMI-IIOP实例:

客户端代码可以参考Java 中 RMI、JNDI、LDAP、JRMP、JMX、JMS那些事儿(上)中的测试项目,可以自己编译HelloClient和HelloServer,也可以用测试项目中编译好的

在命令行启动名称服务器(java自带):

start orbd -ORBInitialPort 1050

命令行开启服务端HelloServer并配置远程调试,关于如何用IDEA进行远程调试,可以参考这里开头提到的方法

java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 HelloServer

当然不远程调试直接看结果也行,直接启动

java HelloServer

命令行开启客户端

Java HelloClient

此时会弹出计算器,成功远程调试的话,可以看到如下调用栈

EvilMessage.readObejct()中执行命令

题外话:weblogic的安装调试文章

那么weblogic中的RMI-IIOP呢?关于 Java 中的 RMI-IIOP这篇文章中有提到关于weblogic RMI-IIOP的利用,在它的基础上进行了一些研究,Using WebLogic’s RMI over IIOP讲到几种weblogic使用RMI-IIOP客户端的几种方式,包括:

1.独立RMI客户端(配合jndi,不使用weblogic的任何东西)
2.WebLogic客户端
3.J2EE clients
4.CORBA/IDL clients

前两种方式的区别,看来只是JNDI_FACTORY设置上的区别

之前研究weblogic T3反序列化的时候,在weblogic上部署过Helloserver应用,有sayhello()方法可以利用,尝试设置两种JNDI_FACTORY调用一下,使用第二种JNDI_FACTORY成功调用sayHello()方法

那么修改一下weblogic T3协议的POC,其实只是把RMI修改为了IIOP,发现jtaTransactionManager利用链执行成功,向本地jrmplisten发出了jrmp请求

看一下流量,在remove()方法调用时,发送remove__java_lang_Object请求,流量中有恶意数据,但没有找到aced魔术头

猜测在服务端进行了特殊的解析后,再反序列化数据,看一下调用栈,可以看到后半部分的执行链跟前面原生RMI-IIOP的执行链很像,前面是从CDRInputStream.read_value()触发,这里是从weblogic中IIOPInputStream.read_value()触发,(read_value这个点,在19年的议题上也提到了)

这里请求会首先给clusterableServerRef.invoke()处理,根据不同的invoker调用this.invoker.invoke(),然后这里调用了Mejb_dj5nps_HomeImpl_WLSkel.invoke(),因为是“remove”,所以进入case 6分支调用IIOPInputStream.readObject(),在read_value()方法中解析IIOPInputStream数据并触发反序列化,这个就是利用remove()方法的POC

 

0x02 CVE-2020-2551

Lucifaer师傅的分析文章提到了用bind()方法进行利用,这个也是互联网上主流的利用方式,跟一下调用栈

跟前面一样,这里请求会首先给clusterableServerRef.invoke()处理,根据不同的invoker调用this.invoker.invoke(),这里调用了CobraServerRef.invoke(),然后在 _NamingContextAnyImplBase._invoke()中,因为va1是“bind_any”,所以进入case 0分支,调用IIOPInputStream.read_any()方法,后面还是会调用IIOPInputStream.read_value()触发反序列,之前说在流量中没有看到aced魔术头,是因为在IIOPInputStream中有一套解析方式,IIOPInputStream的hex-value形式如下,其中包含类名和字段信息:

最终会调用恶意类的readObejct()方法

看了一下补丁,发现跟2015年T3反序列化利用的补丁的是同个位置

WebLogic CVE-2020-2551漏洞分析的测试中,看到CVE-2020-2551过滤的类位置也是在weblogic.iiop.Utils类中

但是在本地测试的时候,weblogic10.3.6打上2015年的补丁,并没有触发isBlacklisted()函数(但是在MsgAbbrevInputStream与InboundMsgAbbrev中都有调用isBlacklisted()进行黑名单验证,奇奇怪怪。。。)

这次CVE-2020-2551的补丁,在weblogic.iiop.Utils.LoadClass()中添加了过滤的verifyclassermitted()方法

黑名单过滤了恶意类,其中包括JtaTransactionManager的父类com.bea.core.repackaged.springframework.transaction.support.AbstractPlatformTransactionManager,这个类是weblogic自带的,十分危险,在看补丁的时候就有一个想法,因为606行的验证是在LoadClass()之后,那如果在加载className的时候进行了类的加载执行恶意静态代码块不就绕过防御了吗?这个后面再说

 

0x03 模拟IIOP协议构造POC

用JAVA程序写的POC有网络问题,就直接打本地weblogic服务可以,打docker容器或者外网机器不行,提到这个问题的分析文章:

手把手教你解决Weblogic CVE-2020-2551 POC网络问题

漫谈 WebLogic-CVE-2020-2551

下面对POC进行调试,可以参考前面remove那个,也可以参考Y4er的,前面两篇文章提到两种解决方法:

  • 修改weblogic.jar包并重新打包
  • 模拟IIOP协议

我都进行了尝试,重新打包weblogic后,会报java.lang.NoSuchMethodError:weblogic.security.subject.SubjectManager.installCESubjectManager错误,但是没有找到解决方案

所以就尝试模拟IIOP协议,先在POC下断点调试

发现在new InitialContext(env)时,调用EndPointImpl.sendReceive()中发送和接收了两个包

LocateReply中包含IOR信息,这里要理解什么是IOR,它的作用是,在RMI-IIOP客户端利用IIOP协议与服务端对象进行交互时,用来提供IIOP通信需要的host和port,还有红框部分Object_key用来区分服务端不同的对象

在模拟IIOP协议时,需要重点关注的是Object_key,host跟ip其实并不影响,之前开始测试的时候,是直接把所有的包重新重放一遍,发送resolve_any的时候返回location forward

GIOP的官方文档中讲到location forward时,表示Object_key是会变化的,不同次请求返回的Object_key可能不同(这里说的Object_key就是数据包中的key address),这个Object_key,前面也有说到,是在用IIOP协议时,用来区别跟哪个对象进行通信,这个值需要在LocateReply中动态获取

最后这里没有选择模拟remove(),而是模拟bind()方法发出的IIOP请求,因为请求比较少,看下本地正常利用的数据包

发送LocateRequest,接收data,通过正则匹配获取LocateReply中的key address

手动设置恶意jrmp服务器(rmi://…)地址,间隔1秒发送bind_any包,因为这里的利用链发出的jrmp请求不是用DGCClient,所以不受JEP290的影响,可以通过jrmplisten进行利用

POC在docker环境中测试成功,可以用vulhub这个SSRF环境,设置IP为宿主机,docker成功获取宿主机jrmp请求,具体代码放在Github

 

0x04 验证猜想

前面有提到利用codebase加载远程代码绕过检测的想法,研究过JNDI攻击的同学应该清楚,codebase可以用来指定远程类的位置,如果codebase可控并且程序允许远程加载类,那么就可以加载远程恶意类执行静态代码块中的恶意代码。

阅读代码可知,weblogic.iiop.Utils.lodaClass()第二个参数表示codebase,这个参数在IIOPInputStream.read_value()中读取,即为var8参数,1659行调用readIndirectingRepositoryId(var8),最终会调用weblogic.iiop.Utils.lodaClass(),要执行1644行和1659行代码,需要(va4r & 1)=1,(va4 & 6)=2,所以var4的值为3

readIndirectingRepositoryId到getClassFromId的调用栈,最后会执行304行loadclass()

看一下bind_any数据包,其实就是通过GIOP Header与GIOP Request组合而成的,GIOP Request中又包括key address(与LocateteReply中一致)、ServiceContextList与stub_data,var4的值就是stub_data中的x7fxffxffx02,所以(va4r & 1)=0,(va4 & 6)=2,不会执行1644行代码设置codebase

我们通过修改下面第一个框x7fxffxffx02为x00x00x00x03,第二个框添加codebase长度与值信息,具体的代码放在Github,可以看到还进行了一个对齐操作,这是一个坑点,因为在读取后面类信息前,会判断下一个字节的位置是不是4的倍数,如果不是会忽略掉一些位数,比如下一个字节的位置是1,则会忽略掉3个字节,直接从位置为4的这个字节读起,这个位置是相对整个bind_any包来说的,这里如果字节不是4的倍数,进行补0操作

还有一个问题,看一下之前readIndirectingRepositoryId到getClassFromId的调用栈,中间会经过findClassInfo()函数,在这里,如果已经加载过某个类,类ID信息会保存起来,findClassInfo()时直接返回类信息,不会进入weblogic.iiop.Utils.getClassFromID()函数

所以在进行测试时,每次都要改一下类名

不管怎样,最后总算是成功模拟IIOP协议修改了codebase值,并执行weblogic.iiop.Utils.getClassFromId()函数。

不幸的是,在获取RMIURLClassFinder的时候返回NULL,RMIEnvironment.getEnvironment().isNetworkClassLoadingEnabled()函数返回false

原因是ServerMBeanImpl中_NetworkClassLoadingEnable参数的值为False

想要看看在哪个weblogic配置文件中设置了这个参数,但是没有找到。。。

 

总结

在学习这个漏洞的过程中,发现需要很多的前置知识,比如java反序列化,RMI,JNDI那些东西,相关的学习可以参考这个文章专栏,虽然最后修改codebase进行利用失败,但还是收获不少。

(完)