报告编号:B6-2020-080601
报告来源:360CERT
报告作者:Hu3sky
更新日期:2020-08-06
0x01 漏洞简述
2020年06月08日,360CERT监测到 IBM官方
发布了 WebSphere远程代码执行
的风险通告,该漏洞编号为 CVE-2020-4450
,漏洞等级:严重
,漏洞评分:9.8分
。
此漏洞由IIOP
协议上的反序列化造成,未经身份认证的攻击者可以通过IIOP
协议远程攻击WebSphere Application Server
,在目标服务端执行任意代码,获取系统权限,进而接管服务器。
对此,360CERT建议广大用户及时安装最新补丁,做好资产自查以及预防工作,以免遭受黑客攻击。
0x02 风险等级
360CERT对该漏洞的评定结果如下
评定方式 | 等级 |
---|---|
威胁等级 | 严重 |
影响面 | 广泛 |
360CERT评分 | 9.8分 |
0x03 影响版本
WebSphere Application Server: 9.0.0.0 to 9.0.5.4
WebSphere Application Server: 8.5.0.0 to 8.5.5.17
WebSphere Application Server: 8.0.0.0 to 8.0.0.15
WebSphere Application Server: 7.0.0.0 to 7.0.0.45
0x04 漏洞详情
按照zdi
给出的分析,iiop
的拦截是在com.ibm.ws.Transaction.JTS.TxServerInterceptor#receive_request
,那么下断点进行远程调试,由于websphere
自己实现了一套iiop
,所以想要走到iiop
触发反序列化的点还需要构造满足条件的iiop
客户端,这里需要走到demarshalContext
方法,前提是满足validOtsContext
为true
,也就是需要serviceContext
不为null
。
假如我们已经构造了serviceContext
不为null
,继续往下看,将serviceContext.context_data
传入demarshalContext
方法。
调用createCDRInputStream
创建CDRInputStream
,实际上生成的是EncoderInputStream
,CDRInputStream
的子类,之后调用EncoderInputStream#read_any
方法。
之后的调用有些繁琐,就不列出来了,调用栈为:
serviceContext赋值
由于需要serviceContext
不为null
,才能走到demarshalContext
方法体里面,在com.ibm.rmi.iiop.Connection#setConnectionContexts
方法中,该方法如下:
setConnectionContexts
方法可以对ServiceContext
属性进行设置,但是我们需要从iiop
生成的默认上下文中获取存储着的当前Connection
信息。
参考@iswin
师傅的文章,可以知道,在com.ibm.rmi.iiop.GIOPImpl
里,存在一个getConnection
方法,可以获取当前上下文的Connection
实例对象,
不过该方法需要传递当前ior
参数,而GIOPImpl
的对象在orb
里,
这些都能通过反射从defaultContext
中获取。
恶意数据流构造
看一下数据流是怎么被解包的,具体在demarshalContext
方法里,也就是构造完ServeicContext
下一步要执行的。
与之对应的,marshalContext
方法里有相应的数据包生成代码,所以只需要将关键代码单独掏出来,再把PropagationContext
对象构造一下,就能生成gadgets
对象的数据流。
构造的关键代码为:
gadgets
既然iiop已经通了,那么我们就根据zdi
给出的gadgets
进行构造,首先需要明确的是,反序列化的入口是org.apache.wsif.providers.ejb.WSIFPort_EJB
(因为websphere
自身类加载器的问题,导致现有的gadgets
都无法利用,所以我们只能基于新的挖掘的类来构造gadgets
)。
这里我们利用的是
handle.getEJBObject
方法,handle
是一个Handle
类型,在实现了Handle
接口的类中,能够进行利用的是com.ibm.ejs.container.EntityHandle
这个类,事实上,我们在对handle
进行赋值的时候,比较复杂,需要反射多个对象。
我们来看一下他的getEJBObject
方法。
这里有几处需要注意的:
lookup加载本地factory
先来看第一处,也就是lookup
方法,这里的homeJNDIName
是我们在反序列化流程中可以控制的。于是,在能够出网的情况下,可以指向我们的rmi
服务。
这里首先会调用
registry.lookup
,获取我们在rmi
上bind
的对象,由于jdk版本过高的原因,所以导致com.sun.jndi.ldap.object.trustURLCodebase
选项默认被设置为false
,也就是说,我们不能利用jndi去远程服务器上利用URLClassLoader
动态加载加载类,只能实例化本地的Factory
,这里利用的方式是加载本地类,具体细节参考:Exploiting JNDI Injections in Java。
简单来说,正常情况下,jndi
的利用会在RegistryImpl_Stub.lookup
之后返回一个ReferenceWrapper
的客户端代理类,ReferenceWrapper
提供了三个参数,className
,factory
,factoryLocation
,如果本地加载不到className
,那么就会去factoryLocation
上加载factory
,大致流程为:
而现在不能远程去加载factoryLocation
,那么我们寻求一个本地factory
来实例化,并利用该factory
的getObjectInstance
方法,根据zdi
提供的漏洞细节,满足条件的factory
是org.apache.wsif.naming.WSIFServiceObjectFactory
。
前边的调用栈是这样的,
com.sun.jndi.rmi.registry.RegistryContext#lookup
com.sun.jndi.rmi.registry.RegistryContext#decodeObject
javax.naming.spi.NamingManager#getObjectInstance
在javax.naming.spi.NamingManager#getObjectInstance
,获取到的builder
为OSGiObjectFactoryBuilder
,然后调用OSGiObjectFactoryBuilder
的getObjectInstance
,这里会实例化helper
为DirObjectFactoryHelper
,是ObjectFactoryHelper
的子类,没有getObjectInstance
方法,所以会调用ObjectFactoryHelper#getObjectInstance
之后的调用栈为
org.apache.aries.jndi.OSGiObjectFactoryBuilder#getObjectInstance
org.apache.aries.jndi.ObjectFactoryHelper#getObjectInstance
org.apache.aries.jndi.ObjectFactoryHelper#getObjectInstanceUsingObjectFactoryBuilders
rmi
服务端可以在reference
对象里对factory
进行设置,当然这个factory
需要满足一些条件,当调用WSIFServiceObjectFactory.getObjectInstance
,我们看一下这个方法。这里wsdlLoc
,serviceNS
等值都可以在rmi
端通过Reference
进行设置。
这里会对ref
进行判断,也就是Reference
对象的第一个参数,也是可控的。我们需要走到下面的判断里,也就是让ref
为WSIFServiceStubRef
,原因是这样的,需要回过来看到前面,
我们需要指定返回代理的类型是EJBHome
。这里有两个地方需要指定接口的类型,一个是narrow
第二个参数homeClass
,一个是在Reference
指定className
,用来控制返回的代理类型。
WSIF web服务
这里的getObjectInstance
调用将从远程URL
初始化WSIF
服务,该URL指向可由攻击者控制的远程XML定义,在远程xml里,我们可以将方法进行映射,这里只说个概念,后面再仔细说。
指定生成的stub的接口类型
当ref
是WSIFServiceStubRef
类型的时候,可以通过className
来指定生成stub
的接口类型,这样就能生成实现EJHome
接口的代理,这里前面已经提到过了,具体在rmi
服务端通过Reference
进行设置。
创建动态代理
这里会在getStub
里创建代理类。
根据提供的接口,最终返回
WSIFClientProxy
代理类。
el表达式注入
接着,在this.findFindByPrimaryKey
获取homeClass
接口的findByPrimaryKey
方法。
之后,就会调用动态代理类的
invke
方法,传入findFindByPrimaryKey
和this.key
,也就是方法的参数。
在
WSIFClientProxy.invoke
的方法里,会调用WSIFPort
实现类的createOperation
方法。
这个
createOperation
方法就能将方法进行映射,这里我们可以将findFindByPrimaryKey
方法映射为本地存在的方法,比如javax.el.ELProcessor
的eval
方法,这里的映射就主要体现在之前提到的WSIF web
服务里,需要将映射内容体现在我们自定义的远程xml文件里。主要语法可以参考:WSDL Java Extension,具体的调用就在自定义rmi服务上进行设置,之后的调用栈如下:
最终在
ELProcessor#eval
方法里执行el
表达式。
漏洞利用
利用成功的截图如下:
版本修复
在官网下载补丁进行分析,发现对iiop
拦截类的Helper
类TxInterceptorHelper
进行了修改,在demarshalContext
方法里,将read_any
方法取消了,无法进入后续反序列化流程。
WSIFPort_EJB
类也进行了修改,在readObject
方法里,将原本的handle.getEJBObject
方法给取消了。
0x05 时间线
2020-06-04 IBM发布预警
2020-06-08 360CERT发布预警
2020-07-21 ZDI发布分析报告
2020-08-05 360CERT发布分析报告