CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/metasploit/framework/login_scanner/http.rb
Views: 1904
1
require 'metasploit/framework/login_scanner/base'
2
require 'metasploit/framework/login_scanner/rex_socket'
3
4
module Metasploit
5
module Framework
6
module LoginScanner
7
#
8
# HTTP-specific login scanner.
9
#
10
class HTTP
11
include Metasploit::Framework::LoginScanner::Base
12
include Metasploit::Framework::LoginScanner::RexSocket
13
14
AUTHORIZATION_HEADER = 'WWW-Authenticate'.freeze
15
DEFAULT_REALM = nil
16
DEFAULT_PORT = 80
17
DEFAULT_SSL_PORT = 443
18
DEFAULT_HTTP_SUCCESS_CODES = [200, 201].append(*(300..309))
19
DEFAULT_HTTP_NOT_AUTHED_CODES = [401]
20
LIKELY_PORTS = [80, 443, 8000, 8080]
21
LIKELY_SERVICE_NAMES = %w[http https]
22
PRIVATE_TYPES = [:password]
23
REALM_KEY = Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN
24
25
# @!attribute uri
26
# @return [String] The path and query string on the server to
27
# authenticate to.
28
attr_accessor :uri
29
30
# @!attribute uri
31
# @return [String] HTTP method, e.g. "GET", "POST"
32
attr_accessor :method
33
34
# @!attribute user_agent
35
# @return [String] the User-Agent to use for the HTTP requests
36
attr_accessor :user_agent
37
38
# @!attribute vhost
39
# @return [String] the Virtual Host name for the target Web Server
40
attr_accessor :vhost
41
42
# @!attribute evade_uri_encode_mode
43
# @return [String] The type of URI encoding to use
44
attr_accessor :evade_uri_encode_mode
45
46
# @!attribute evade_uri_full_url
47
# @return [Boolean] Whether to use the full URL for all HTTP requests
48
attr_accessor :evade_uri_full_url
49
50
# @!attribute evade_pad_method_uri_count
51
# @return [Integer] How many whitespace characters to use between the method and uri
52
attr_accessor :evade_pad_method_uri_count
53
54
# @!attribute evade_pad_uri_version_count
55
# @return [Integer] How many whitespace characters to use between the uri and version
56
attr_accessor :evade_pad_uri_version_count
57
58
# @!attribute evade_pad_method_uri_type
59
# @return [String] What type of whitespace to use between the method and uri
60
attr_accessor :evade_pad_method_uri_type
61
62
# @!attribute evade_pad_uri_version_type
63
# @return [String] What type of whitespace to use between the uri and version
64
attr_accessor :evade_pad_uri_version_type
65
66
# @!attribute evade_method_random_valid
67
# @return [Boolean] Whether to use a random, but valid, HTTP method for request
68
attr_accessor :evade_method_random_valid
69
70
# @!attribute evade_method_random_invalid
71
# @return [Boolean] Whether to use a random invalid, HTTP method for request
72
attr_accessor :evade_method_random_invalid
73
74
# @!attribute evade_method_random_case
75
# @return [Boolean] Whether to use random casing for the HTTP method
76
attr_accessor :evade_method_random_case
77
78
# @!attribute evade_version_random_valid
79
# @return [Boolean] Whether to use a random, but valid, HTTP version for request
80
attr_accessor :evade_version_random_valid
81
82
# @!attribute evade_version_random_invalid
83
# @return [Boolean] Whether to use a random invalid, HTTP version for request
84
attr_accessor :evade_version_random_invalid
85
86
# @!attribute evade_uri_dir_self_reference
87
# @return [Boolean] Whether to insert self-referential directories into the uri
88
attr_accessor :evade_uri_dir_self_reference
89
90
# @!attribute evade_uri_dir_fake_relative
91
# @return [Boolean] Whether to insert fake relative directories into the uri
92
attr_accessor :evade_uri_dir_fake_relative
93
94
# @!attribute evade_uri_use_backslashes
95
# @return [Boolean] Whether to use back slashes instead of forward slashes in the uri
96
attr_accessor :evade_uri_use_backslashes
97
98
# @!attribute evade_pad_fake_headers
99
# @return [Boolean] Whether to insert random, fake headers into the HTTP request
100
attr_accessor :evade_pad_fake_headers
101
102
# @!attribute evade_pad_fake_headers_count
103
# @return [Integer] How many fake headers to insert into the HTTP request
104
attr_accessor :evade_pad_fake_headers_count
105
106
# @!attribute evade_pad_get_params
107
# @return [Boolean] Whether to insert random, fake query string variables into the request
108
attr_accessor :evade_pad_get_params
109
110
# @!attribute evade_pad_get_params_count
111
# @return [Integer] How many fake query string variables to insert into the request
112
attr_accessor :evade_pad_get_params_count
113
114
# @!attribute evade_pad_post_params
115
# @return [Boolean] Whether to insert random, fake post variables into the request
116
attr_accessor :evade_pad_post_params
117
118
# @!attribute evade_pad_post_params_count
119
# @return [Integer] How many fake post variables to insert into the request
120
attr_accessor :evade_pad_post_params_count
121
122
# @!attribute evade_shuffle_get_params
123
# @return [Boolean] Randomize order of GET parameters
124
attr_accessor :evade_shuffle_get_params
125
126
# @!attribute evade_shuffle_post_params
127
# @return [Boolean] Randomize order of POST parameters
128
attr_accessor :evade_shuffle_post_params
129
130
# @!attribute evade_uri_fake_end
131
# @return [Boolean] Whether to add a fake end of URI (eg: /%20HTTP/1.0/../../)
132
attr_accessor :evade_uri_fake_end
133
134
# @!attribute evade_uri_fake_params_start
135
# @return [Boolean] Whether to add a fake start of params to the URI (eg: /%3fa=b/../)
136
attr_accessor :evade_uri_fake_params_start
137
138
# @!attribute evade_header_folding
139
# @return [Boolean] Whether to enable folding of HTTP headers
140
attr_accessor :evade_header_folding
141
142
# @!attribute ntlm_use_ntlmv2_session
143
# @return [Boolean] Whether to activate the 'Negotiate NTLM2 key' flag, forcing the use of a NTLMv2_session
144
attr_accessor :ntlm_use_ntlmv2_session
145
146
# @!attribute ntlm_use_ntlmv2
147
# @return [Boolean] Whether to use NTLMv2 instead of NTLM2_session when 'Negotiate NTLM2' is enabled
148
attr_accessor :ntlm_use_ntlmv2
149
150
# @!attribute ntlm_send_lm
151
# @return [Boolean] Whether to always send the LANMAN response (except when NTLMv2_session is specified)
152
attr_accessor :ntlm_send_lm
153
154
# @!attribute ntlm_send_ntlm
155
# @return [Boolean] Whether to activate the 'Negotiate NTLM key' flag, indicating the use of NTLM responses
156
attr_accessor :ntlm_send_ntlm
157
158
# @!attribute ntlm_send_spn
159
# @return [Boolean] Whether to send an avp of type SPN in the NTLMv2 client blob.
160
attr_accessor :ntlm_send_spn
161
162
# @!attribute ntlm_use_lm_key
163
# @return [Boolean] Activate the 'Negotiate Lan Manager Key' flag, using the LM key when the LM response is sent
164
attr_accessor :ntlm_use_lm_key
165
166
# @!attribute ntlm_domain
167
# @return [String] The NTLM domain to use during authentication
168
attr_accessor :ntlm_domain
169
170
# @!attribute digest_auth_iis
171
# @return [Boolean] Whether to conform to IIS digest authentication mode.
172
attr_accessor :digest_auth_iis
173
174
# @!attribute http_username
175
# @return [String]
176
attr_accessor :http_username
177
178
# @!attribute http_password
179
# @return [String]
180
attr_accessor :http_password
181
182
# @!attribute kerberos_authenticator_factory
183
# @return [Func<username, password, realm> : Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::HTTP] A factory method for creating a kerberos authenticator
184
attr_accessor :kerberos_authenticator_factory
185
186
# @!attribute keep_connection_alive
187
# @return [Boolean] Whether to keep the connection open after a successful login
188
attr_accessor :keep_connection_alive
189
190
# @!attribute http_success_codes
191
# @return [Array][Int] list of valid http response codes
192
attr_accessor :http_success_codes
193
194
validate :validate_http_codes
195
196
validates :uri, presence: true, length: { minimum: 1 }
197
198
validates :method,
199
presence: true,
200
length: { minimum: 1 }
201
202
# (see Base#check_setup)
203
def check_setup
204
http_client = Rex::Proto::Http::Client.new(
205
host, port, {'Msf' => framework, 'MsfExploit' => framework_module}, ssl, ssl_version, proxies, http_username, http_password
206
)
207
request = http_client.request_cgi(
208
'uri' => uri,
209
'method' => method
210
)
211
212
begin
213
# Use _send_recv instead of send_recv to skip automatic
214
# authentication
215
response = http_client._send_recv(request)
216
rescue ::EOFError, Errno::ETIMEDOUT, OpenSSL::SSL::SSLError, Rex::ConnectionError, ::Timeout::Error
217
return 'Unable to connect to target'
218
end
219
220
if authentication_required?(response)
221
return false
222
end
223
224
'No authentication required'
225
end
226
227
# Sends a HTTP request with Rex
228
#
229
# @param [Hash] opts native support includes the following (also see Rex::Proto::Http::Request#request_cgi)
230
# @option opts [String] 'host' The remote host
231
# @option opts [Integer] 'port' The remote port
232
# @option opts [Boolean] 'ssl' The SSL setting, TrueClass or FalseClass
233
# @option opts [String] 'proxies' The proxies setting
234
# @option opts [Credential] 'credential' A credential object
235
# @option opts [Rex::Proto::Http::Client] 'http_client' object that can be used by the function
236
# @option opts ['Hash'] 'context' A context
237
# @raise [Rex::ConnectionError] One of these errors has occurred: EOFError, Errno::ETIMEDOUT, Rex::ConnectionError, ::Timeout::Error
238
# @return [Rex::Proto::Http::Response] The HTTP response
239
# @return [NilClass] An error has occurred while reading the response (see #Rex::Proto::Http::Client#read_response)
240
def send_request(opts)
241
close_client = !opts.key?(:http_client)
242
cli = opts.fetch(:http_client) { create_client(opts) }
243
244
begin
245
cli.connect
246
req = cli.request_cgi(opts)
247
248
# Authenticate by default
249
res = if opts['authenticate'].nil? || opts['authenticate']
250
cli.send_recv(req)
251
else
252
cli._send_recv(req)
253
end
254
rescue ::EOFError, Errno::ETIMEDOUT, Errno::ECONNRESET, Rex::ConnectionError, OpenSSL::SSL::SSLError, ::Timeout::Error => e
255
raise Rex::ConnectionError, e.message
256
ensure
257
# If we didn't create the client, don't close it
258
if close_client
259
cli.close
260
end
261
end
262
263
res
264
end
265
266
267
# Attempt a single login with a single credential against the target.
268
#
269
# @param credential [Credential] The credential object to attempt to
270
# login with.
271
#
272
# @return [Result] A Result object indicating success or failure
273
def attempt_login(credential)
274
result_opts = {
275
credential: credential,
276
status: Metasploit::Model::Login::Status::INCORRECT,
277
proof: nil,
278
host: host,
279
port: port,
280
protocol: 'tcp'
281
}
282
283
if ssl
284
result_opts[:service_name] = 'https'
285
else
286
result_opts[:service_name] = 'http'
287
end
288
289
request_opts = {'credential'=>credential, 'uri'=>uri, 'method'=>method}
290
if keep_connection_alive
291
request_opts[:http_client] = create_client(request_opts)
292
end
293
294
begin
295
response = send_request(request_opts)
296
if response && http_success_codes.include?(response.code)
297
result_opts.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL, proof: response.headers)
298
end
299
rescue Rex::ConnectionError => e
300
result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e)
301
rescue ::Rex::Proto::Kerberos::Model::Error::KerberosError => e
302
mapped_err = Metasploit::Framework::LoginScanner::Kerberos.login_status_for_kerberos_error(e)
303
result_opts.merge!(status: mapped_err, proof: e)
304
ensure
305
if request_opts.key?(:http_client)
306
if result_opts[:status] == Metasploit::Model::Login::Status::SUCCESSFUL
307
result_opts[:connection] = request_opts[:http_client]
308
else
309
request_opts[:http_client].close
310
end
311
end
312
end
313
314
Result.new(result_opts)
315
end
316
317
protected
318
319
# Returns a boolean value indicating whether the request requires authentication or not.
320
#
321
# @param [Rex::Proto::Http::Response] response The response received from the HTTP endpoint
322
# @return [Boolean] True if the request required authentication; otherwise false.
323
def authentication_required?(response)
324
return false unless response
325
326
self.class::DEFAULT_HTTP_NOT_AUTHED_CODES.include?(response.code) &&
327
response.headers[self.class::AUTHORIZATION_HEADER]
328
end
329
330
private
331
332
def create_client(opts)
333
rhost = opts['host'] || host
334
rport = opts['rport'] || port
335
cli_ssl = opts['ssl'] || ssl
336
cli_ssl_version = opts['ssl_version'] || ssl_version
337
cli_proxies = opts['proxies'] || proxies
338
username = opts['credential'] ? opts['credential'].public : http_username
339
password = opts['credential'] ? opts['credential'].private : http_password
340
realm = opts['credential'] ? opts['credential'].realm : nil
341
context = opts['context'] || { 'Msf' => framework, 'MsfExploit' => framework_module}
342
343
kerberos_authenticator = nil
344
if kerberos_authenticator_factory
345
kerberos_authenticator = kerberos_authenticator_factory.call(username, password, realm)
346
end
347
348
http_logger_subscriber = framework_module.nil? ? nil : Rex::Proto::Http::HttpLoggerSubscriber.new(logger: framework_module)
349
res = nil
350
cli = Rex::Proto::Http::Client.new(
351
rhost,
352
rport,
353
context,
354
cli_ssl,
355
cli_ssl_version,
356
cli_proxies,
357
username,
358
password,
359
kerberos_authenticator: kerberos_authenticator,
360
subscriber: http_logger_subscriber
361
)
362
configure_http_client(cli)
363
364
if realm
365
cli.set_config('domain' => realm)
366
end
367
368
cli
369
end
370
371
# This method is responsible for mapping the caller's datastore options to the
372
# Rex::Proto::Http::Client configuration parameters.
373
def configure_http_client(http_client)
374
http_client.set_config(
375
'vhost' => vhost || host,
376
'agent' => user_agent
377
)
378
379
possible_params = {
380
'uri_encode_mode' => evade_uri_encode_mode,
381
'uri_full_url' => evade_uri_full_url,
382
'pad_method_uri_count' => evade_pad_method_uri_count,
383
'pad_uri_version_count' => evade_pad_uri_version_count,
384
'pad_method_uri_type' => evade_pad_method_uri_type,
385
'pad_uri_version_type' => evade_pad_uri_version_type,
386
'method_random_valid' => evade_method_random_valid,
387
'method_random_invalid' => evade_method_random_invalid,
388
'method_random_case' => evade_method_random_case,
389
'version_random_valid' => evade_version_random_valid,
390
'version_random_invalid' => evade_version_random_invalid,
391
'uri_dir_self_reference' => evade_uri_dir_self_reference,
392
'uri_dir_fake_relative' => evade_uri_dir_fake_relative,
393
'uri_use_backslashes' => evade_uri_use_backslashes,
394
'pad_fake_headers' => evade_pad_fake_headers,
395
'pad_fake_headers_count' => evade_pad_fake_headers_count,
396
'pad_get_params' => evade_pad_get_params,
397
'pad_get_params_count' => evade_pad_get_params_count,
398
'pad_post_params' => evade_pad_post_params,
399
'pad_post_params_count' => evade_pad_post_params_count,
400
'shuffle_get_params' => evade_shuffle_get_params,
401
'shuffle_post_params' => evade_shuffle_post_params,
402
'uri_fake_end' => evade_uri_fake_end,
403
'uri_fake_params_start' => evade_uri_fake_params_start,
404
'header_folding' => evade_header_folding,
405
'usentlm2_session' => ntlm_use_ntlmv2_session,
406
'use_ntlmv2' => ntlm_use_ntlmv2,
407
'send_lm' => ntlm_send_lm,
408
'send_ntlm' => ntlm_send_ntlm,
409
'SendSPN' => ntlm_send_spn,
410
'UseLMKey' => ntlm_use_lm_key,
411
'domain' => ntlm_domain,
412
'DigestAuthIIS' => digest_auth_iis
413
}
414
415
# Set the parameter only if it is not nil
416
possible_params.each_pair do |k,v|
417
next if v.nil?
418
http_client.set_config(k => v)
419
end
420
421
http_client
422
end
423
424
# This method sets the sane defaults for things
425
# like timeouts and TCP evasion options
426
def set_sane_defaults
427
self.connection_timeout ||= 20
428
self.uri = '/' if self.uri.blank?
429
self.method = 'GET' if self.method.blank?
430
self.http_success_codes = DEFAULT_HTTP_SUCCESS_CODES if self.http_success_codes.nil?
431
432
# Note that this doesn't cover the case where ssl is unset and
433
# port is something other than a default. In that situation,
434
# we don't know what the user has in mind so we have to trust
435
# that they're going to do something sane.
436
if !(self.ssl) && self.port.nil?
437
self.port = self.class::DEFAULT_PORT
438
self.ssl = false
439
elsif self.ssl && self.port.nil?
440
self.port = self.class::DEFAULT_SSL_PORT
441
elsif self.ssl.nil? && self.port == self.class::DEFAULT_PORT
442
self.ssl = false
443
elsif self.ssl.nil? && self.port == self.class::DEFAULT_SSL_PORT
444
self.ssl = true
445
end
446
447
if self.ssl.nil?
448
self.ssl = false
449
end
450
451
nil
452
end
453
454
# Combine the base URI with the target URI in a sane fashion
455
#
456
# @param [Array<String>] target_uri the target URL
457
# @return [String] the final URL mapped against the base
458
def normalize_uri(*target_uri)
459
if target_uri.count == 1
460
(uri.to_s + '/' + target_uri.first.to_s).gsub(%r{/+}, '/')
461
else
462
new_str = target_uri * '/'
463
new_str = new_str.gsub!('//', '/') while new_str.index('//')
464
465
# Makes sure there's a starting slash
466
unless new_str[0,1] == '/'
467
new_str = '/' + new_str
468
end
469
470
new_str
471
end
472
end
473
474
private
475
476
def validate_http_codes
477
errors.add(:http_success_codes, "HTTP codes must be an Array") unless @http_success_codes.is_a?(Array)
478
@http_success_codes.each do |code|
479
next if code >= 200 && code < 400
480
errors.add(:http_success_codes, "Invalid HTTP code provided #{code}")
481
end
482
end
483
end
484
end
485
end
486
end
487
488
489