Skip to content

Commit 0cd2bc5

Browse files
committed
Land rapid7#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 ([email protected]). This updates the LDAP library.
2 parents 0e273bf + fc2d5c2 commit 0cd2bc5

File tree

1 file changed

+121
-96
lines changed

1 file changed

+121
-96
lines changed

lib/metasploit/framework/ldap/client.rb

Lines changed: 121 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -24,112 +24,137 @@ def ldap_connect_opts(rhost, rport, connect_timeout, ssl: true, opts: {})
2424

2525
case opts[:ldap_auth]
2626
when Msf::Exploit::Remote::AuthOption::SCHANNEL
27-
pfx_path = opts[:ldap_cert_file]
28-
raise Msf::ValidationError, 'The LDAP::CertFile option is required when using SCHANNEL authentication.' if pfx_path.blank?
29-
raise Msf::ValidationError, 'The SSL option must be enabled when using SCHANNEL authentication.' if ssl != true
27+
raise Msf::ValidationError, 'The SSL option must be enabled when using SCHANNEL authentication.' unless ssl
3028

31-
unless ::File.file?(pfx_path) && ::File.readable?(pfx_path)
32-
raise Msf::ValidationError, 'Failed to load the PFX certificate file. The path was not a readable file.'
33-
end
34-
35-
begin
36-
pkcs = OpenSSL::PKCS12.new(File.binread(pfx_path), '')
37-
rescue StandardError => e
38-
raise Msf::ValidationError, "Failed to load the PFX file (#{e})"
39-
end
40-
41-
connect_opts[:auth] = {
42-
method: :sasl,
43-
mechanism: 'EXTERNAL',
44-
initial_credential: '',
45-
challenge_response: true
46-
}
47-
connect_opts[:encryption] = {
48-
method: :start_tls,
49-
tls_options: {
50-
verify_mode: OpenSSL::SSL::VERIFY_NONE,
51-
cert: pkcs.certificate,
52-
key: pkcs.key
53-
}
54-
}
29+
connect_opts.merge!(ldap_auth_opts_scahnnel(opts))
5530
when Msf::Exploit::Remote::AuthOption::KERBEROS
56-
raise Msf::ValidationError, 'The Ldap::Rhostname option is required when using Kerberos authentication.' if opts[:ldap_rhostname].blank?
57-
raise Msf::ValidationError, 'The DOMAIN option is required when using Kerberos authentication.' if opts[:domain].blank?
58-
59-
offered_etypes = Msf::Exploit::Remote::AuthOption.as_default_offered_etypes(opts[:ldap_krb_offered_enc_types])
60-
raise Msf::ValidationError, 'At least one encryption type is required when using Kerberos authentication.' if offered_etypes.empty?
61-
62-
kerberos_authenticator = Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::LDAP.new(
63-
host: opts[:domain_controller_rhost].blank? ? nil : opts[:domain_controller_rhost],
64-
hostname: opts[:ldap_rhostname],
65-
realm: opts[:domain],
66-
username: opts[:username],
67-
password: opts[:password],
68-
framework: opts[:framework],
69-
framework_module: opts[:framework_module],
70-
cache_file: opts[:ldap_krb5_cname].blank? ? nil : opts[:ldap_krb5_cname],
71-
ticket_storage: opts[:kerberos_ticket_storage],
72-
offered_etypes: offered_etypes
73-
)
74-
75-
connect_opts[:auth] = {
76-
method: :sasl,
77-
mechanism: 'GSS-SPNEGO',
78-
initial_credential: proc do
79-
kerberos_result = kerberos_authenticator.authenticate
80-
kerberos_result[:security_blob]
81-
end,
82-
challenge_response: true
83-
}
31+
connect_opts.merge!(ldap_auth_opts_kerberos(opts))
8432
when Msf::Exploit::Remote::AuthOption::NTLM
85-
ntlm_client = RubySMB::NTLM::Client.new(
86-
opts[:username],
87-
opts[:password],
88-
workstation: 'WORKSTATION',
89-
domain: opts[:domain].blank? ? '.' : opts[:domain],
90-
flags:
91-
RubySMB::NTLM::NEGOTIATE_FLAGS[:UNICODE] |
92-
RubySMB::NTLM::NEGOTIATE_FLAGS[:REQUEST_TARGET] |
93-
RubySMB::NTLM::NEGOTIATE_FLAGS[:NTLM] |
94-
RubySMB::NTLM::NEGOTIATE_FLAGS[:ALWAYS_SIGN] |
95-
RubySMB::NTLM::NEGOTIATE_FLAGS[:EXTENDED_SECURITY] |
96-
RubySMB::NTLM::NEGOTIATE_FLAGS[:KEY_EXCHANGE] |
97-
RubySMB::NTLM::NEGOTIATE_FLAGS[:TARGET_INFO] |
98-
RubySMB::NTLM::NEGOTIATE_FLAGS[:VERSION_INFO]
99-
)
100-
101-
negotiate = proc do |challenge|
102-
ntlmssp_offset = challenge.index('NTLMSSP')
103-
type2_blob = challenge.slice(ntlmssp_offset..-1)
104-
challenge = [type2_blob].pack('m')
105-
type3_message = ntlm_client.init_context(challenge)
106-
type3_message.serialize
107-
end
108-
109-
connect_opts[:auth] = {
110-
method: :sasl,
111-
mechanism: 'GSS-SPNEGO',
112-
initial_credential: ntlm_client.init_context.serialize,
113-
challenge_response: negotiate
114-
}
33+
connect_opts.merge!(ldap_auth_opts_ntlm(opts))
11534
when Msf::Exploit::Remote::AuthOption::PLAINTEXT
116-
connect_opts[:auth] = {
117-
method: :simple,
118-
username: opts[:username],
119-
password: opts[:password]
120-
}
35+
connect_opts.merge!(ldap_auth_opts_plaintext(opts))
12136
when Msf::Exploit::Remote::AuthOption::AUTO
122-
unless opts[:username].blank? # plaintext if specified
123-
connect_opts[:auth] = {
124-
method: :simple,
125-
username: opts[:username],
126-
password: opts[:password]
127-
}
37+
if opts[:username].present? && opts[:domain].present?
38+
connect_opts.merge!(ldap_auth_opts_ntlm(opts))
39+
elsif opts[:username].present?
40+
connect_opts.merge!(ldap_auth_opts_plaintext(opts))
12841
end
12942
end
13043

13144
connect_opts
13245
end
46+
47+
private
48+
49+
def ldap_auth_opts_kerberos(opts)
50+
auth_opts = {}
51+
raise Msf::ValidationError, 'The Ldap::Rhostname option is required when using Kerberos authentication.' if opts[:ldap_rhostname].blank?
52+
raise Msf::ValidationError, 'The DOMAIN option is required when using Kerberos authentication.' if opts[:domain].blank?
53+
54+
offered_etypes = Msf::Exploit::Remote::AuthOption.as_default_offered_etypes(opts[:ldap_krb_offered_enc_types])
55+
raise Msf::ValidationError, 'At least one encryption type is required when using Kerberos authentication.' if offered_etypes.empty?
56+
57+
kerberos_authenticator = Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::LDAP.new(
58+
host: opts[:domain_controller_rhost].blank? ? nil : opts[:domain_controller_rhost],
59+
hostname: opts[:ldap_rhostname],
60+
realm: opts[:domain],
61+
username: opts[:username],
62+
password: opts[:password],
63+
framework: opts[:framework],
64+
framework_module: opts[:framework_module],
65+
cache_file: opts[:ldap_krb5_cname].blank? ? nil : opts[:ldap_krb5_cname],
66+
ticket_storage: opts[:kerberos_ticket_storage],
67+
offered_etypes: offered_etypes
68+
)
69+
70+
auth_opts[:auth] = {
71+
method: :sasl,
72+
mechanism: 'GSS-SPNEGO',
73+
initial_credential: proc do
74+
kerberos_result = kerberos_authenticator.authenticate
75+
kerberos_result[:security_blob]
76+
end,
77+
challenge_response: true
78+
}
79+
auth_opts
80+
end
81+
82+
def ldap_auth_opts_ntlm(opts)
83+
auth_opts = {}
84+
ntlm_client = RubySMB::NTLM::Client.new(
85+
opts[:username],
86+
opts[:password],
87+
workstation: 'WORKSTATION',
88+
domain: opts[:domain].blank? ? '.' : opts[:domain],
89+
flags:
90+
RubySMB::NTLM::NEGOTIATE_FLAGS[:UNICODE] |
91+
RubySMB::NTLM::NEGOTIATE_FLAGS[:REQUEST_TARGET] |
92+
RubySMB::NTLM::NEGOTIATE_FLAGS[:NTLM] |
93+
RubySMB::NTLM::NEGOTIATE_FLAGS[:ALWAYS_SIGN] |
94+
RubySMB::NTLM::NEGOTIATE_FLAGS[:EXTENDED_SECURITY] |
95+
RubySMB::NTLM::NEGOTIATE_FLAGS[:KEY_EXCHANGE] |
96+
RubySMB::NTLM::NEGOTIATE_FLAGS[:TARGET_INFO] |
97+
RubySMB::NTLM::NEGOTIATE_FLAGS[:VERSION_INFO]
98+
)
99+
100+
negotiate = proc do |challenge|
101+
ntlmssp_offset = challenge.index('NTLMSSP')
102+
type2_blob = challenge.slice(ntlmssp_offset..-1)
103+
challenge = [type2_blob].pack('m')
104+
type3_message = ntlm_client.init_context(challenge)
105+
type3_message.serialize
106+
end
107+
108+
auth_opts[:auth] = {
109+
method: :sasl,
110+
mechanism: 'GSS-SPNEGO',
111+
initial_credential: ntlm_client.init_context.serialize,
112+
challenge_response: negotiate
113+
}
114+
auth_opts
115+
end
116+
117+
def ldap_auth_opts_plaintext(opts)
118+
auth_opts = {}
119+
auth_opts[:auth] = {
120+
method: :simple,
121+
username: opts[:username],
122+
password: opts[:password]
123+
}
124+
auth_opts
125+
end
126+
127+
def ldap_auth_opts_scahnnel(opts)
128+
auth_opts = {}
129+
pfx_path = opts[:ldap_cert_file]
130+
raise Msf::ValidationError, 'The LDAP::CertFile option is required when using SCHANNEL authentication.' if pfx_path.blank?
131+
132+
unless ::File.file?(pfx_path) && ::File.readable?(pfx_path)
133+
raise Msf::ValidationError, 'Failed to load the PFX certificate file. The path was not a readable file.'
134+
end
135+
136+
begin
137+
pkcs = OpenSSL::PKCS12.new(File.binread(pfx_path), '')
138+
rescue StandardError => e
139+
raise Msf::ValidationError, "Failed to load the PFX file (#{e})"
140+
end
141+
142+
auth_opts[:auth] = {
143+
method: :sasl,
144+
mechanism: 'EXTERNAL',
145+
initial_credential: '',
146+
challenge_response: true
147+
}
148+
auth_opts[:encryption] = {
149+
method: :start_tls,
150+
tls_options: {
151+
verify_mode: OpenSSL::SSL::VERIFY_NONE,
152+
cert: pkcs.certificate,
153+
key: pkcs.key
154+
}
155+
}
156+
auth_opts
157+
end
133158
end
134159
end
135160
end

0 commit comments

Comments
 (0)