基础设施
vSphere Client分为UI层、Java服务层、后端层,前端通过RESTful API与基于Spring MVC和OSGI框架的Java服务层进行通信。
vCenter可以安装部署至ESXi,也可以导入镜像中的OVA文件部署,漏洞环境的ISO文件可从此处获取。需要注意的是VCSA的最小部署规格tiny(微型)需要2核10G内存300G硬盘,本文采用的方案为在ESXi的物理机上嵌套安装ESXi虚拟机,并在嵌套安装的ESXi虚拟机中安装VCSA。保持根路由与一级ESXi物理机VLAN为空(或不变/或为4095),将一级ESXi与二级ESXi的虚拟交换机设置为4095(或某个固定值),将二级ESXi中运行的VCSA接入二级虚拟交换机,并同时开启该链路上的各级交换机的混杂模式。
File Read
由EAM用户运行的服务存在文件读取,Windows上可获取帐号密码。
影响版本:
- 6.0 <= vCenter Server <= 6.5 f < 6.5 u1
POC:https://1.1.1.1/eam/vib?id=C:\ProgramData\VMware\vCenterServer\cfg\vmware-vpx\vcdb.properties
CVE-2021-21972
默认启用的vROps插件(com.vmware.vropspluginui.mvc)ServicesController类的uploadova接口存在未授权访问,可利用路径穿越将文件解压至特定目录实现getshell。
影响版本:
- 7.0 <= vCenter Server < 7.0 U1c
- 6.7 <= vCenter Server < 6.7 U3l
- 6.5 1e <= vCenter Server < 6.5 U3n
- 4.x <= Cloud Foundation (vCenter Server) < 4.2
- 3.x <= Cloud Foundation (vCenter Server) < 3.10.1.2
EXP:VMware vCenter Server 7.0 - Unauthenticated File Upload
漏洞分析
用root帐号ssh连上vCenter,找到vropsplugin-service.jar利用python3 -m http.server 8010
下载到本地反编译:
从post上传的输入流中解析tar遍历文件,创建File类拼接目录时存在../../
目录穿越,可将文件解压至vsphere-ui用户有权限的目录。切入该用户并查找可写目录:
1 | su vsphere-ui |
.ssh
可写就能上传公钥,并通过安装VCSA时通常都会开启的SSH服务连上来,但我们先看一下shadow文件:
1 |
|
由冒号分隔的各项分别代表:
- 用户名
- 哈希算法、盐、哈希密码
- 最后一次密码修改时间(距1970年1月1日天数)
- 最小密码修改间隔时间
- 密码过期时间
- 密码过期前警告时间
- 密码过期后宽限时间
- 账号失效时间
- 保留字段
看到密码过期时间为90天,因此在安装90天后即使写入了公钥登录也会提示密码过期,需要提供原密码并修改密码:
vsphere-ui用户的第二项为!
,这表示该用户未设置密码(与空密码不同),所以也就没法修改密码。。。
写文件getshell需要充分利用各种服务,遍历找出存在有jsp的web.xml并与可写目录交叉对比:
由/usr/lib/vmware-vsphere-ui/server/configuration/tomcat-server.xml
查到监听端口为5090,再由rhttpproxy反向代理找到web访问路径:
1 |
|
- 靠前的redirect表示将http重定向到https,后面的allow表示允许https访问
META-INF/MANIFEST.MF
中的Web-ContextPath
也会标识web路径
最后将webshell释放至/usr/lib/vmware-vsphere-ui/server/work/deployer/s/global/42/0/h5ngc.war/resources/
目录或其子目录,即可解析并由https://1.1.1.1/ui/resources/webshell.jsp
访问
该路径中的42并非是固定数值,会随着重装重启等行为发生改变,所以构造上传包时可以暴力批量添加,并利用解压时的容错性释放。
6.7U2及之后的版本,会在服务启动时判断如果存在work目录就删除,也就是说Web是跑在内存里面的。这时对于6.7U2及更新的6.7版本可以将webshell释放至/usr/lib/vmware-vsphere-ui/server/static/resources/libs/
目录作为后门,待其重启后会被加载运行。对于7.0版本static后面的resources会跟一串动态数字路径,能够在请求的返回包中获取到。
- Windows由于权限控制并不严格,可以将webshell释放至
C:\ProgramData\VMware\vCenterServer\data\perfcharts\tc-instance\webapps\statsreport\
目录,会以system权限运行
CVE-2021-21985
默认启用的Virtual SAN Health Check插件(vsan-h5-client.zip)/rest/*
接口存在未授权访问,可利用不安全的反射调用实现getshell。
影响版本:
- 7.0 <= vCenter Server < 7.0 U2b
- 6.7 <= vCenter Server < 6.7 U3n
- 6.5 <= vCenter Server < 6.5 U3p
- 4.x <= Cloud Foundation (vCenter Server) < 4.2.1
- 3.x <= Cloud Foundation (vCenter Server) < 3.10.2.1
EXP:vCenterExp
漏洞分析
从官方通告能够猜到漏洞入口是vsan插件的未授权访问,为了减少干扰代码的影响,我们对漏洞修复前后的两个版本(VMware-VCSA-all-6.7.0-18010531、VMware-VCSA-all-6.7.0-17713310)进行对比分析。
挂载或解压对应ISO,将VMware VCSA/vcsa
路径下的OVA文件导入虚拟机,由CUI开启ssh服务便于后续操作。
用root帐号ssh连上vCenter,定位到vsan-h5-client插件再通过python httpserver下载。
1 |
|
不是我不知道scp这个东西,photon linux的特殊结构导致了没法直接用scp传输文件:
至于为什么用8010端口,是因为严格的iptables规则中这个端口在白名单里而且没被占用。
1 |
|
分别将两个版本的压缩包下载下来解压,IDEA对比h5-vsan-context.jar,看到新版本中对/rest/*
路径添加了authenticationFilter
过滤器。
具体类实现中拦截了未登录的请求,并返回401状态码:
另一处变动是h5-vsan-service.jar中ProxygenController类的invokeService方法,通过isAnnotationPresent判断只有方法存在TsService
接口才会反射调用,感觉就是设置方法白名单了。
invokeService方法会被invokeServiceWithJson
或invokeServiceWithMultipartFormData
调用,两个方法都是从URL路径中取beanIdOrClassName
和methodName
的值、从HTTP请求体中取methodInput
的值,并经过格式化处理后作为入参传给invokeService方法。
invokeService方法反射获取类进而注入bean,反射获取所有public方法并遍历,通过ProxygenSerializer类的deserializeMethodInput转化为方法对象后反射调用。(6.7不同小版本的代码有细微差异)
由21982的分析已经知道vCenter会由rhttpproxy反代复用端口,通过META-INF/MANIFEST.MF
和web.xml
可以知道vsan插件部署的Web路径为ui/h5-vsan/rest/*
,再结合各级的RequestMapping路由映射注解,推出漏洞入口就是通过https://1.1.1.1/ui/h5-vsan/rest/proxy/service/{beanIdOrClassName}/{methodName}
触发环境中类危险方法调用。有TP5的RCE那味了,但并不可以用Runtime.exec直接莽,因为getBean时只会在beanMap
中查找,动态调试可以看到内存中加载的Map:
- 在
/etc/vmware/vmware-vmon/svcCfgfiles/vsphere-ui.json
中添加启动参数-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8010
后执行service-control --restart vsphere-ui
重启服务,IDEA中添加Remote JVM Debug
配置Attach to remote JVM
模式远程调试,如果不是用8010端口得开一下防火墙:iptables -P INPUT ACCEPT
所以接下来就是在一堆bean里查找危险方法构建利用链了,在vsan-h5-client/plugins/h5-vsan-service/META-INF/spring/base/*.xml
配置文件中找到它们的定义,所有scope都是缺省的singleton
而且没有配置lazy-init
,也就是说这些bean都会在spring项目启动时单例加载。
看到org.springframework.beans.factory.config.MethodInvokingFactoryBean
方法和它的继承链,在官方API文档可以清晰地看到其继承自父类MethodInvoker的多个方法:
invoke方法源码如下,由targetObject
、preparedMethod
调用静态方法,ReflectionUtils.makeAccessible
修改方法可见性,getArguments
获取参数:
1 | public Object invoke() throws InvocationTargetException, IllegalAccessException { |
直接调用静态方法并不需要targetObject
,通过setTargetObject
将其设置为null。getArguments
取的就是arguments
的值,可以通过setArguments
将其设置为Obejct[]的JNDI远程方法(RMI/LDAP)。向上跟进preparedMethod
可以看到如下反向调用链:
1 | preparedMethod |
至此这条利用链的入口和链尾已经成型:{beanIdOrClassName}/{methodName} -> ... -> MethodInvokingFactoryBean -> MethodInvoker -> JNDI(javax.naming.InitialContext.doLookup) -> 恶意RMI/LDAP服务器提供的远程对象
,搜索配置文件中class为MethodInvokingFactoryBean
的bean就可以找到能作为连接链两端的部分:
1 | vsanProviderUtils_setVmodlHelper |
- bean配置中没写id时,name属性可以起到类似的作用
直接调用FactoryBean实际上是其getObject方法返回的对象,而我们需要的是MethodInvokingFactoryBean
自身,因此在调用这些bean时要在前面加上&
。利用bean饿汉式单例的特性,可以通过POST请求依次调用各个set方法赋值构造利用链:
1 | &{beanName}/setTargetObject |
但。。。是。。。随着vCenter版本的不断更新,其photon linux搭载的jdk版本也在不断更新,6.7初始版本的8u71可以直接打,中间更高些jdk版本可能就需要打rmi bypass或者ldap,到漏洞修复前一个小版本jdk已经是8u281了,按照刚从火星回来的我肤浅的了解,目前似乎还没有公开的byapss 8u241+的方法(这里不是太确定,说错了欢迎拍砖)
而且vCenter通常部署在内网深处,不一定有那么好的出网环境加载恶意方法。回顾来看目前具有调用任意类任意静态方法的能力,被反向移植的jfr包的静态方法writeGeneratedASM中存在FileOutputStream,通过java.lang.System.setProperty静态方法将SAVE_GENERATED
设置为true,这样就能将字节数组写入指定位置的以.class
结尾的文件中。
1 | public static void writeGeneratedASM(String className, byte[] bytes) { |
但输入参数都会被ProxygenSerializer类的deserializeMethodInput方法格式化成Object[]
,要怎么得到byte[]
类型的参数呢。巧的是当prepare
时argTypes
类型不正确导致的异常,会经由以下调用栈并最终转化为需要的byte数组Orz:
1 | this.findMatchingMethod |
再利用tomcat中的静态方法copyInternal即可实现对写入的.class
文件的拷贝和重命名。没有能未授权访问且解析jsp的地方时,可以利用JNI机制由System.load加载native方法调用上传的恶意so,不过如果目标不出网也不好解决命令回显的问题。
对此漏洞作者(rr yyds)利用vmodlContext
这个bean对应类com.vmware.vim.vmomi.core.types.impl.VmodContextImpl
(vropsplugin-service.jar)的loadVmodlPackage
方法,会经由NonValidatingClassPathXmlApplicationContext调用父类ClassPathXmlApplicationContext的构造方法从我们可控的vmodPackage
加载,该Spring类构造方法支持远程加载解析xml中的SpEL表达式执行命令。
vmodPackage
参数传递过程中通过getContextFileNameForPackage
加载/context.xml
,并通过其重载方法将.
替换为/
,xml内容中不能用标准IP和域名,可以用十进制型IP绕过。但这样依然是反向远程加载xml文件,不出网的环境就会很蛋疼,所以现在要解决的问题是如何通过正向访问将恶意xml送进去。可以想到通过data协议传入base64编码的xml数据,可是Java的URL类默认只支持http、https、file、jar。
Java不行!Python行!位于/usr/lib/vmware-vpx/vsan-health/pyMoVsan/
的VsanHttpProvider.py
存在一个未授权访问SSRF,匹配vsanHealth/vum/driverOfflineBundle/
的请求内容,由urlopen发包并zip解压匹配返回*offline_bundle.*
文件内容。/etc/hosts
的存在也能帮助绕过.
的限制,算是锦上添花了。
1 |
|
现在就只剩下最后一个问题就是如何拿到命令执行的回显,rr的解决方案是发现可以调用到systemProperties
的getProperty
方法拿到属性,所以执行命令时只需将结果由system.setProperty
存入再读出。也可以利用方法执行时的报错将执行结果带出。
- System类有一个本质为Hashtable的Properties类型的props静态成员变量,单个JVM实例共享,不同JVM实例隔离
总结一下该漏洞可以调用环境中任意类静态方法,比较直接的就是通过JNDI加载远程恶意方法,进一步能够写入和重命名任意文件通过JNI加载so中的native方法,也可以利用Java中的SSRF套娃Python的SSRF实现SpEL注入RCE。
参考链接
Creating and Deploying Plug-In Packages
Spring Framework Documentation
Unauthorized RCE in VMware vCenter
Vcenter Server CVE-2021-21985 RCE PAYLOAD
A Quick Look at CVE-2021–21985 VCenter Pre-Auth RCE