diff --git a/lib/metasploit/framework/ldap/client.rb b/lib/metasploit/framework/ldap/client.rb index 668006fced..1a7648bb82 100644 --- a/lib/metasploit/framework/ldap/client.rb +++ b/lib/metasploit/framework/ldap/client.rb @@ -24,112 +24,137 @@ module Metasploit case opts[:ldap_auth] when Msf::Exploit::Remote::AuthOption::SCHANNEL - pfx_path = opts[:ldap_cert_file] - raise Msf::ValidationError, 'The LDAP::CertFile option is required when using SCHANNEL authentication.' if pfx_path.blank? - raise Msf::ValidationError, 'The SSL option must be enabled when using SCHANNEL authentication.' if ssl != true + raise Msf::ValidationError, 'The SSL option must be enabled when using SCHANNEL authentication.' unless ssl - 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 - - connect_opts[:auth] = { - method: :sasl, - mechanism: 'EXTERNAL', - initial_credential: '', - challenge_response: true - } - connect_opts[:encryption] = { - method: :start_tls, - tls_options: { - verify_mode: OpenSSL::SSL::VERIFY_NONE, - cert: pkcs.certificate, - key: pkcs.key - } - } + connect_opts.merge!(ldap_auth_opts_scahnnel(opts)) when Msf::Exploit::Remote::AuthOption::KERBEROS - raise Msf::ValidationError, 'The Ldap::Rhostname option is required when using Kerberos authentication.' if opts[:ldap_rhostname].blank? - raise Msf::ValidationError, 'The DOMAIN option is required when using Kerberos authentication.' if opts[:domain].blank? - - offered_etypes = Msf::Exploit::Remote::AuthOption.as_default_offered_etypes(opts[:ldap_krb_offered_enc_types]) - raise Msf::ValidationError, 'At least one encryption type is required when using Kerberos authentication.' if offered_etypes.empty? - - kerberos_authenticator = Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::LDAP.new( - host: opts[:domain_controller_rhost].blank? ? nil : opts[:domain_controller_rhost], - hostname: opts[:ldap_rhostname], - realm: opts[:domain], - username: opts[:username], - password: opts[:password], - framework: opts[:framework], - framework_module: opts[:framework_module], - cache_file: opts[:ldap_krb5_cname].blank? ? nil : opts[:ldap_krb5_cname], - ticket_storage: opts[:kerberos_ticket_storage], - offered_etypes: offered_etypes - ) - - connect_opts[:auth] = { - method: :sasl, - mechanism: 'GSS-SPNEGO', - initial_credential: proc do - kerberos_result = kerberos_authenticator.authenticate - kerberos_result[:security_blob] - end, - challenge_response: true - } + connect_opts.merge!(ldap_auth_opts_kerberos(opts)) when Msf::Exploit::Remote::AuthOption::NTLM - ntlm_client = RubySMB::NTLM::Client.new( - opts[:username], - opts[:password], - workstation: 'WORKSTATION', - domain: opts[:domain].blank? ? '.' : opts[:domain], - flags: - RubySMB::NTLM::NEGOTIATE_FLAGS[:UNICODE] | - RubySMB::NTLM::NEGOTIATE_FLAGS[:REQUEST_TARGET] | - RubySMB::NTLM::NEGOTIATE_FLAGS[:NTLM] | - RubySMB::NTLM::NEGOTIATE_FLAGS[:ALWAYS_SIGN] | - RubySMB::NTLM::NEGOTIATE_FLAGS[:EXTENDED_SECURITY] | - RubySMB::NTLM::NEGOTIATE_FLAGS[:KEY_EXCHANGE] | - RubySMB::NTLM::NEGOTIATE_FLAGS[:TARGET_INFO] | - RubySMB::NTLM::NEGOTIATE_FLAGS[:VERSION_INFO] - ) - - negotiate = proc do |challenge| - ntlmssp_offset = challenge.index('NTLMSSP') - type2_blob = challenge.slice(ntlmssp_offset..-1) - challenge = [type2_blob].pack('m') - type3_message = ntlm_client.init_context(challenge) - type3_message.serialize - end - - connect_opts[:auth] = { - method: :sasl, - mechanism: 'GSS-SPNEGO', - initial_credential: ntlm_client.init_context.serialize, - challenge_response: negotiate - } + connect_opts.merge!(ldap_auth_opts_ntlm(opts)) when Msf::Exploit::Remote::AuthOption::PLAINTEXT - connect_opts[:auth] = { - method: :simple, - username: opts[:username], - password: opts[:password] - } + connect_opts.merge!(ldap_auth_opts_plaintext(opts)) when Msf::Exploit::Remote::AuthOption::AUTO - unless opts[:username].blank? # plaintext if specified - connect_opts[:auth] = { - method: :simple, - username: opts[:username], - password: opts[:password] - } + if opts[:username].present? && opts[:domain].present? + connect_opts.merge!(ldap_auth_opts_ntlm(opts)) + elsif opts[:username].present? + connect_opts.merge!(ldap_auth_opts_plaintext(opts)) end end connect_opts end + + private + + def ldap_auth_opts_kerberos(opts) + auth_opts = {} + raise Msf::ValidationError, 'The Ldap::Rhostname option is required when using Kerberos authentication.' if opts[:ldap_rhostname].blank? + raise Msf::ValidationError, 'The DOMAIN option is required when using Kerberos authentication.' if opts[:domain].blank? + + offered_etypes = Msf::Exploit::Remote::AuthOption.as_default_offered_etypes(opts[:ldap_krb_offered_enc_types]) + raise Msf::ValidationError, 'At least one encryption type is required when using Kerberos authentication.' if offered_etypes.empty? + + kerberos_authenticator = Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::LDAP.new( + host: opts[:domain_controller_rhost].blank? ? nil : opts[:domain_controller_rhost], + hostname: opts[:ldap_rhostname], + realm: opts[:domain], + username: opts[:username], + password: opts[:password], + framework: opts[:framework], + framework_module: opts[:framework_module], + cache_file: opts[:ldap_krb5_cname].blank? ? nil : opts[:ldap_krb5_cname], + ticket_storage: opts[:kerberos_ticket_storage], + offered_etypes: offered_etypes + ) + + auth_opts[:auth] = { + method: :sasl, + mechanism: 'GSS-SPNEGO', + initial_credential: proc do + kerberos_result = kerberos_authenticator.authenticate + kerberos_result[:security_blob] + end, + challenge_response: true + } + auth_opts + end + + def ldap_auth_opts_ntlm(opts) + auth_opts = {} + ntlm_client = RubySMB::NTLM::Client.new( + opts[:username], + opts[:password], + workstation: 'WORKSTATION', + domain: opts[:domain].blank? ? '.' : opts[:domain], + flags: + RubySMB::NTLM::NEGOTIATE_FLAGS[:UNICODE] | + RubySMB::NTLM::NEGOTIATE_FLAGS[:REQUEST_TARGET] | + RubySMB::NTLM::NEGOTIATE_FLAGS[:NTLM] | + RubySMB::NTLM::NEGOTIATE_FLAGS[:ALWAYS_SIGN] | + RubySMB::NTLM::NEGOTIATE_FLAGS[:EXTENDED_SECURITY] | + RubySMB::NTLM::NEGOTIATE_FLAGS[:KEY_EXCHANGE] | + RubySMB::NTLM::NEGOTIATE_FLAGS[:TARGET_INFO] | + RubySMB::NTLM::NEGOTIATE_FLAGS[:VERSION_INFO] + ) + + negotiate = proc do |challenge| + ntlmssp_offset = challenge.index('NTLMSSP') + type2_blob = challenge.slice(ntlmssp_offset..-1) + challenge = [type2_blob].pack('m') + type3_message = ntlm_client.init_context(challenge) + type3_message.serialize + end + + auth_opts[:auth] = { + method: :sasl, + mechanism: 'GSS-SPNEGO', + initial_credential: ntlm_client.init_context.serialize, + challenge_response: negotiate + } + auth_opts + end + + def ldap_auth_opts_plaintext(opts) + auth_opts = {} + auth_opts[:auth] = { + method: :simple, + username: opts[:username], + password: opts[:password] + } + auth_opts + end + + def ldap_auth_opts_scahnnel(opts) + auth_opts = {} + pfx_path = opts[:ldap_cert_file] + raise Msf::ValidationError, 'The LDAP::CertFile option is required when using SCHANNEL authentication.' if pfx_path.blank? + + 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 + + auth_opts[:auth] = { + method: :sasl, + mechanism: 'EXTERNAL', + initial_credential: '', + challenge_response: true + } + auth_opts[:encryption] = { + method: :start_tls, + tls_options: { + verify_mode: OpenSSL::SSL::VERIFY_NONE, + cert: pkcs.certificate, + key: pkcs.key + } + } + auth_opts + end end end end