Skip to content

Commit fc2d5c2

Browse files
committed
Fix ldap auto authentication
When the auth method is set to auto and a domain is specific, use NTLM.
1 parent 1d764c1 commit fc2d5c2

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)