作者:Hu3sky@360CERT
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
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
表达式。
漏洞利用
利用成功的截图如下:
版本修复
在官网下载补丁进行分析,发现对反序列化入口WSIFPort_EJB
进行了修改,在readObject
方法里,将原本的handle.getEJBObject
方法给取消了,这样,也就把这个链的入口给杀死了。
0x05 时间线
2020-06-04 IBM发布预警
2020-06-08 360CERT发布预警
2020-07-21 ZDI发布分析报告
2020-08-05 360CERT发布分析报告