Land #18935, Fix LDAP auto auth
This PR fixes a common user mistake when authenticating with LDAP modules. Now users can specify either the USERNAME (user) and DOMAIN (domain.local) datastore options or the original format of just the USERNAME in the UPN format (user@domain.local). This updates the LDAP library.
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user