前阵子vcenter爆出了CVE-2021-21985
漏洞,师傅们对这个漏洞提出了多种不同的利用方案,都非常的精彩,因此我搭建好了vcenter的环境,并对师傅们公开的利用方法进行了分析。
环境搭建
本来打算通过OVA来安装,试了7.0和6.5的版本,由于各种坑试了两天都没搭建成功,最后无奈在windows下进行安装,需要注意的是在SSO设置域名时,如果是在内网搭建的没有域名,改一下本地的hosts文件随便给个域名就可以了,最终选择6.7的版本进行安装。
接下来开启debug模式,参考Vcenter Server CVE-2021-21985 RCE PAYLOAD,直接在 C:\ProgramData\VMware\vCenterServer\cfg\vmware-vmon\svcCfgfiles\vsphere-ui.json 文件中取消remote debug注释即可。
切换到C:\Program Files\VMware\vCenter Server\bin
目录下执行service-control --restart vsphere-ui
重启vsphere-ui服务。
漏洞原理
这个漏洞原理并不复杂,在com.vmware.vsan.client.services.ProxygenController#invokeService
中实现,根据传入的beanIdOrClassName参数获取bean,再通过methodName获取要调用bean的方法名,再通过body里的methodInput参数获取方法对应的参数,最终通过Invoke反射调用,所以可以调用当前环境中任意bean的任意public方法。
漏洞利用
这个漏洞的主要亮点在于寻找利用的方式,通过上面的分析我们发现通过这个漏洞可以调用任意Bean的任意方法,所以漏洞利用的思路就在于找到一个Bean,里面存在某个恶意的方法,可以让我们执行命令或者进行文件写入,下面分析下网上已经公开的利用方式。
JNDI
这种利用方式来自于Vcenter Server CVE-2021-21985 RCE PAYLOAD,利用者找到了一个vsanProviderUtils_setVmodlHelper
,本质上是org.springframework.beans.factory.config.MethodInvokingFactoryBean
。
在MethodInvokingFactoryBean的父类org.springframework.util.MethodInvoker
中的invoke方法中使用了反射调用,也就是如果我们能控制targetObject,preparedMethod和Arguments参数,就可以调用任意类的任意static方法。
看了这种利用方式,我心里有很多疑问,下面我将通过提问的方式来解决我对于这种利用方式的疑惑。
为什么说是任意类的static方法?
在if语句中会进行判断,如果调用方法不为static则会抛异常。
能否调用private方法?
在org.springframework.util.ReflectionUtils#makeAccessible(java.lang.reflect.Method)
中,判断是否为public方法,如果不是则设置访问属性。因此通过这个点可以调用private方法。
是否需要获取targetObject对象?
由于调用的是static方法,因此不用获取targetObject对象,通过调用setTargetObject设置TargetObject类型为null即可。
如何设置preparedMethod属性?
在org.springframework.util.MethodInvoker#prepare
中,通过targetClass.getMethod(targetMethod, argTypes)
为methodObject属性赋值。
targetClass的获取有两种方式,当staticMethod的属性不为空时,可以通过staticMethod获取targetClass属性,当staticMethod属性为空时,则只能通过setTargetClass来为targetClass属性赋值。
分析下这两种方式的利弊,当设置staticMethod属性时,会将staticMethod的最后一个.之前的内容赋值给targetMethod,resolveClassName方法将targetMethod当作类名,通过 ClassUtils.forName
得到对应的Class对象,也会取出最后一个.后的内容赋值给targetMethod属性,因此不用手工为targetMethod属性赋值。
当通过setTargetClass来获取时,只能通过传入Class对象的方式,显然在当前的漏洞环境中无法传递一个Class对象,因此不能使用setTargetClass的方式为targetClass属性赋值。
再看看如何给arguments属性赋值,通过setArguments方法传入一个数组为arguments属性赋值。
最后通过脑图总结下。
为什么可以通过多次请求给Bean不同属性赋值?
spring中bean默认是单例的,并且bean的生命周期从spring容器启动开始一直到spring容器的销毁,因此在spring运行过程中,如果没有修改scope作用域,每个bean都只有一个实例。
为什么传入的beanIdOrClassName参数需要加上&?
由于访问的MethodInvokingFactoryBean是工厂bean,所以bean名称应该以&符号为前缀。
是否有其他的点可以访问到MethodInvokingFactoryBean?
除了vsanProviderUtils_setVmodlHelper是MethodInvokingFactoryBean实例外,还有其他的Bean也是MethodInvokingFactoryBean的实例,也可以调用到MethodInvoker的invoke方法。
1 | vsanCapabilityUtils_setVsanCapabilityCacheManager |
利用过程
- 设置TargetObject属性
1 | POST /ui/h5-vsan/rest/proxy/service/&vsanProviderUtils_setVmodlHelper/setTargetObject HTTP/1.1 |
- 设置Arguments属性
1 | POST /ui/h5-vsan/rest/proxy/service/&vsanProviderUtils_setVmodlHelper/setArguments HTTP/1.1 |
- 设置staticMethod
1 | POST /ui/h5-vsan/rest/proxy/service/&vsanProviderUtils_setVmodlHelper/setStaticMethod HTTP/1.1 |
- 通过prepare为methodObject赋值
1 | POST /ui/h5-vsan/rest/proxy/service/&vsanProviderUtils_setVmodlHelper/prepare HTTP/1.1 |
- invoke反射调用
1 | POST /ui/h5-vsan/rest/proxy/service/&vsanProviderUtils_setVmodlHelper/invoke HTTP/1.1 |
SPEL注入
这种利用方式来自于漏洞的原作者VCSA 6.5-7.0 远程代码执行 CVE-2021-21985 漏洞分析,作者找到了vmodlContext
,这个bean是VmodlContextImpl
的实现类,通过调用这个类的createContext
方法,并传入一个String数组,经过层层调用可以调用到实例化ClassPathXmlApplicationContext
的逻辑,并将传入的String数组的内容赋值到ClassPathXmlApplicationContext
的构造方法参数中,我们可以在远程服务器上创建一个XML文件,通过传入放置XML服务器的地址,让目标加载远程XML文件并对其中的bean的内容进行SPEL解析,由于这个bean的内容是我们可控的,因此可以执行恶意操作。
由于之前对于SPEL的利用方式没有什么了解,因此通过下面解决下面几个问题快速入门下SPEL注入。
什么是SPEL?它可以做什么?
SPEL是一种类似于OGNL的表达式语言,它支持在运行时查询和操纵对象图。
Spring中如何使用SPEL?
SPEL表达式可以与 XML 或基于注解的配置元数据一起使用来定义BeanDefinition,定义表达式的语法为#{expression string},需要注意的是,Class实例通过**T(全限定类名)**来引用,比如我们想执行命令,可以通过下面的代码来构造。
1 | <bean id="Helloclass" class="Hello"> |
ClassPathXmlApplicationContext何时执行SPEL?
首先简单介绍下ClassPathXmlApplicationContext,主要的作用在于从XML中加载定义的Bean,经过测试,当通过new ClassPathXmlApplicationContext("xxx.xml");
时会对property中的value属性和bean的Class属性进行SPEL解析。
在org.springframework.beans.factory.support.AbstractBeanFactory#doResolveBeanClass
中会获取bean标签中class属性配置的内容并通过evaluateBeanDefinitionString
执行SPEL表达式。这里需要注意一下,如果在class属性中传入的SPEL表达式没有返回一个Class对象或String类型的对象,会导致异常并影响后续对property属性的解析。
在org.springframework.beans.factory.support.BeanDefinitionValueResolver#resolveValueIfNecessary
中会对传入的属性值做SPEL解析。
ClassPathXmlApplicationContext为什么会加载远程xml?
在通过ClassPathXmlApplicationContext加载XML时,会调用org.springframework.beans.factory.xml.XmlBeanDefinitionReader#loadBeanDefinitions(org.springframework.core.io.support.EncodedResource)
的getInputStream加载资源,具体调用哪些资源和传入的Resource类型有关系。
查阅官方文档存在UrlResource
,利用Urlresource
可以通过URL加载资源,值得注意的是除了http
协议加载外,也可以通过ftp
,file
协议加载,UrlResource
的getInputStream方法如下。
当然通过UrlResource
加载远程XML的方式只能在服务器出网的情况下使用,如果服务器不出网,就不能通过这种方式。
何时对url做处理的?
根据漏洞作者的描述,传入的url中如果包含.会进行转义,我们看下代码中是怎么处理的,是否有其他的方法绕过限制。VmodlContextImpl#getContextFileNameForPackage(java.lang.String)
中,会对我们传入的url中的.
进行替换,所以我们传入的URL中不能包含.
,并且会在请求url后加上/context_v2.xml
,通过ClassUtil.getCurrentClassLoader().getResource
加载context_v2.xml
,但这个加载器不会加载远程的XML,所以我们远程的文件名必须指定为/context.xml
。对于IP中.
的转换,可以通过IP转数字型IP绕过。
不出网如何利用
作者找到了一个存在ssrf漏洞的点,该漏洞位于VsanHttpProvider.py
中,通过正则匹配获取请求的url,再通过urlopen获取响应内容,调用__doGetFileWithinZip进行解压。
在_doGetFileWithinZip
中获取解压缩后的文件名,对文件名通过传入的正则.*offline_bundle.*
,匹配成功则将文件内容读取并返回。
所以可以制作一个文件名为offline_bundle.xml的内容并制作成zip包,base编码后通过data
协议进行发送。另外虽然利用时会拼接/context.xml
但并不会影响data
协议获取数据。
1 | /vsanHealth/vum/driverOfflineBundle/data://text/plain;base64,UEsDBBQABgAIANNz2FJepoT35wAAAJcBAAASAAAAb2ZmbGluZV9idW5kbGUueG1slZCxTsMwEIb3Sn0H6xiaDLFTWFBUtxtiKAsUidV1jtTg2JXP1JEQ707qdICxXnzD/53++1abobfshIGMdxKWvAaGTvvWuE7C6+6huofNej5b7VE5YmPYkYRDjMdGiJQSp2MYo+9B9Zh8+OQ+dIL0AXslMgLzGZteZpuBzD8+3WXktq6X4u1p+5LRyjiKymn8Q5Nppr1br1XMba+owa7IiummKvfnA7UwCjjfkCUw00p4RGu9tooIWP4k3Hzvig91Utwq1/HnLxdNjyXvMF7mouQ4oC4WWlm9KH9gzS5rs6mz5EnZOP0CUEsBAhQAFAAGAAgA03PYUl6mhPfnAAAAlwEAABIAJAAAAAAAAAAgAAAAAAAAAG9mZmxpbmVfYnVuZGxlLnhtbAoAIAAAAAAAAQAYAJ+7ZnHCaNcBrHTYccJo1wHYmeVXwmjXAVBLBQYAAAAAAQABAGQAAAAXAQAAAAA=/context.xml |
通过上面的分析,可以构造如下请求进行利用。
1 | url:/ui/h5-vsan/rest/proxy/service/vmodlContext/createContext |
如何获取回显?
漏洞作者发现了systemProperties
是properties对象的实例,通过调用getProperty
方法可以获取配置的内容。
可以通过下面的方式调用getProperty
方法获取propterties
对象中的属性。
1 | /ui/h5-vsan/rest/proxy/service/systemProperties/getProperty |
下面只要我们将命令执行的结果写入到properties属性中即可,但我经过分析其实不用通过这种方式,因为vcenter并没有屏蔽错误回显,只要让目标将执行的结果通过报错显示出来即可,因此构造如下XML。
1 |
|
需要注意在请求的最后需要随便加一些字符,否则在拼接/context.XML
后会报错。
1 | url:/ui/h5-vsan/rest/proxy/service/vmodlContext/createContext |
反序列化
这种攻击思路来自于A Quick Look at CVE-2021–21985 VCenter Pre-Auth RCE,这种攻击思路是在iswin师傅找到的MethodInvoker通过反射调用任意类的静态方法的基础上实现的,在vcenter中存在org.apache.catalina.tribes.io.XByteBuffer#deserialize(byte[])
方法,该方法接收我们传入的字节数组并进行反序列化操作。
所以我们只要能调用到deserialize方法并传入字节数组就可以了,那么下一个技术难点在于如何传递一个字节数组。首先我们通过setArguments传入数据时,经过com.vmware.vsan.client.services.ProxygenSerializer#deserializeMethodInput
时会对类型进行转换,无论传入什么类型,都会转换为Object数组类型。
在调用org.springframework.util.MethodInvoker#prepare
方法时,由于我们传入的是Object数组而调用XByteBuffer#deserialize(byte[])
需要一个byte数组,所以这里调用getMethod
会导致异常,进而调用到findMatchingMethod
方法。
在org.springframework.beans.support.ArgumentConvertingMethodInvoker#doFindMatchingMethod
中获取请求方法的参数类型,并调用TypeConverter.convertIfNecessary
,通过这个函数将我们传入的Object数组转换为byte数组,这样就解决了传入byte数组的问题。
下面看下我们传入什么样的数据到convertIfNecessary
才可以转成我们需要的byte数组,跟到convertIfNecessary
代码中进行分析,首先在TypeConverterSupport
中,会调用TypeConverterSupport。doConvert
,在doConvert
中,会将请求委托给TypeConverterDelegate
实现具体的转换逻辑。
由于我们传入的是Object数组,所以会进入到convertToTypedArray
方法。
在convertToTypedArray
将遍历Object数组的每个值,并转换为byte类型,存放到byte数组中。
所以只要传入对象转换的字节数组对应的Int值,就可以通过转换得到对应的字节数组。
下面我生成一个URLDNS利用链的对象,并得到字节数组的内容。
将该数组的内容通过setArguements进行传递。
再通过TypeConverter转换为byte数组
最终将序列化后的内容传递给org.apache.catalina.tribes.io.XByteBuffer#deserialize(byte[])
,完成反序列化操作。
总结
这个漏洞的亮点还是挺多的,无论是通过MethodInvoker
将调用任意bean扩展到调用任意静态方法,还是通过ssrf加载请求获取加载的xml,还有通过触发异常对数据类型转换,都体现出了前辈们漏洞研究上的功底,再次感谢师傅们的分享,最后感谢瓜哥帮我分析反序列化数据传递的问题。
参考
A Quick Look at CVE-2021–21985 VCenter Pre-Auth RCE