diff --git a/lib/metasploit/framework/ldap/client.rb b/lib/metasploit/framework/ldap/client.rb index 63a8ed3296de1..dbdcf0093a312 100644 --- a/lib/metasploit/framework/ldap/client.rb +++ b/lib/metasploit/framework/ldap/client.rb @@ -111,20 +111,11 @@ def ldap_auth_opts_plaintext(opts) def ldap_auth_opts_schannel(opts, ssl) auth_opts = {} - pfx_path = opts[:ldap_cert_file] raise Msf::ValidationError, 'The SSL option must be enabled when using Schannel authentication.' unless ssl raise Msf::ValidationError, 'Can not sign and seal when using Schannel authentication.' if opts.fetch(:sign_and_seal, false) - if pfx_path.present? - unless ::File.file?(pfx_path) && ::File.readable?(pfx_path) - raise Msf::ValidationError, 'Failed to load the PFX certificate file. The path was not a readable file.' - end - - begin - pkcs = OpenSSL::PKCS12.new(File.binread(pfx_path), '') - rescue StandardError => e - raise Msf::ValidationError, "Failed to load the PFX file (#{e})" - end + if opts[:ldap_pkcs12].present? + pkcs = opts[:ldap_pkcs12][:value] else pkcs12_storage = Msf::Exploit::Remote::Pkcs12::Storage.new( framework: opts[:framework], diff --git a/lib/msf/core/exploit/remote/ldap.rb b/lib/msf/core/exploit/remote/ldap.rb index a56762873f0f0..29712d376024f 100644 --- a/lib/msf/core/exploit/remote/ldap.rb +++ b/lib/msf/core/exploit/remote/ldap.rb @@ -75,6 +75,7 @@ def peer # @return [Hash] The options to use when connecting to the target # LDAP server. def get_connect_opts + pkcs12_storage = Msf::Exploit::Remote::Pkcs12::Storage.new(framework: framework, framework_module: self) opts = { username: datastore['LDAPUsername'], password: datastore['LDAPPassword'], @@ -82,7 +83,7 @@ def get_connect_opts base: datastore['BASE_DN'], domain_controller_rhost: datastore['DomainControllerRhost'], ldap_auth: datastore['LDAP::Auth'], - ldap_cert_file: datastore['LDAP::CertFile'], + ldap_pkcs12: datastore['LDAP::CertFile'] ? pkcs12_storage.read_pkcs12_cert_path(datastore['LDAP::CertFile']) : nil, ldap_rhostname: datastore['LDAP::Rhostname'], ldap_krb_offered_enc_types: datastore['LDAP::KrbOfferedEncryptionTypes'], ldap_krb5_cname: datastore['LDAP::Krb5Ccname'], diff --git a/lib/msf/core/exploit/remote/pkcs12/storage.rb b/lib/msf/core/exploit/remote/pkcs12/storage.rb index f7d07d2d6c672..8e23b660ab621 100644 --- a/lib/msf/core/exploit/remote/pkcs12/storage.rb +++ b/lib/msf/core/exploit/remote/pkcs12/storage.rb @@ -16,6 +16,23 @@ def initialize(framework: nil, framework_module: nil) @framework_module = framework_module end + # @param [String] cert_path A path to the file system where a pkcs12 cert is located, or a reference to a core database i.e., "id:123" + # @param [String] cert_pass The certificate password + # @param [String] workspace The workspace to restrict searches to + def read_pkcs12_cert_path(cert_path, cert_pass = '', workspace: nil) + is_readable = ::File.file?(cert_path) && ::File.readable?(cert_path) + raise Msf::ValidationError, 'Failed to load the PFX certificate file. The path was not a readable file.' unless is_readable + data = File.binread(cert_path) + + begin + pkcs12 = OpenSSL::PKCS12.new(data, cert_pass) + rescue StandardError => e + raise Msf::ValidationError, "Failed to load the PFX file (#{e})" + end + + { path: cert_path, value: pkcs12 } + end + # Get stored pkcs12 matching the options query. # # @param [Hash] options The options for matching pkcs12's. diff --git a/modules/auxiliary/admin/kerberos/get_ticket.rb b/modules/auxiliary/admin/kerberos/get_ticket.rb index c5eaf3d13e3b9..009ebb67d2d9a 100644 --- a/modules/auxiliary/admin/kerberos/get_ticket.rb +++ b/modules/auxiliary/admin/kerberos/get_ticket.rb @@ -91,12 +91,8 @@ def initialize(info = {}) def validate_options if datastore['CERT_FILE'].present? - certificate = File.binread(datastore['CERT_FILE']) - begin - @pfx = OpenSSL::PKCS12.new(certificate, datastore['CERT_PASSWORD'] || '') - rescue OpenSSL::PKCS12::PKCS12Error => e - fail_with(Failure::BadConfig, "Unable to parse certificate file (#{e})") - end + pkcs12_storage = Msf::Exploit::Remote::Pkcs12::Storage.new(framework: framework, framework_module: self) + @pfx = pkcs12_storage.read_pkcs12_cert_path(datastore['CERT_FILE'], datastore['CERT_PASSWORD'], workspace: workspace)[:value] if datastore['USERNAME'].blank? && datastore['DOMAIN'].present? fail_with(Failure::BadConfig, 'Domain override provided but no username override provided (must provide both or neither)') diff --git a/modules/auxiliary/scanner/ldap/ldap_login.rb b/modules/auxiliary/scanner/ldap/ldap_login.rb index 8ac6c103c5994..c073ee7786bcf 100644 --- a/modules/auxiliary/scanner/ldap/ldap_login.rb +++ b/modules/auxiliary/scanner/ldap/ldap_login.rb @@ -111,6 +111,7 @@ def run_host(ip) ignore_private: ignore_private ) + pkcs12_storage = Msf::Exploit::Remote::Pkcs12::Storage.new(framework: framework, framework_module: self) opts = { domain: datastore['LDAPDomain'], append_domain: datastore['APPEND_DOMAIN'], @@ -118,7 +119,7 @@ def run_host(ip) proxies: datastore['PROXIES'], domain_controller_rhost: datastore['DomainControllerRhost'], ldap_auth: datastore['LDAP::Auth'], - ldap_cert_file: datastore['LDAP::CertFile'], + ldap_pkcs12: datastore['LDAP::CertFile'] ? pkcs12_storage.read_pkcs12_cert_path(datastore['LDAP::CertFile']) : nil, ldap_rhostname: datastore['Ldap::Rhostname'], ldap_krb_offered_enc_types: datastore['Ldap::KrbOfferedEncryptionTypes'], ldap_krb5_cname: datastore['Ldap::Krb5Ccname'] @@ -167,7 +168,7 @@ def run_host(ip) successful_logins << result if opts[:ldap_auth] == Msf::Exploit::Remote::AuthOption::SCHANNEL # Schannel auth has no meaningful credential information to store in the DB - msg = opts[:ldap_cert_file].nil? ? 'Using stored certificate' : "Cert File #{opts[:ldap_cert_file]}" + msg = opts[:ldap_pkcs12].nil? ? 'Using stored certificate' : "Cert File #{opts[:ldap_pkcs12][:path]} (#{opts[:ldap_pkcs12][:value].certificate.subject})" print_brute level: :good, ip: ip, msg: "Success: '#{msg}'" else create_credential_and_login(credential_data) if result.credential.private