22
33require 'uri'
44require 'digest'
5+
56module Msf
67
78###
@@ -11,6 +12,7 @@ module Msf
1112#
1213###
1314module Exploit ::Remote ::HttpClient
15+
1416 include Msf ::Auxiliary ::Report
1517
1618 #
@@ -47,7 +49,6 @@ def initialize(info = {})
4749 OptBool . new ( 'FingerprintCheck' , [ false , 'Conduct a pre-exploit fingerprint verification' , true ] ) ,
4850 OptString . new ( 'DOMAIN' , [ true , 'The domain to use for Windows authentication' , 'WORKSTATION' ] ) ,
4951 OptFloat . new ( 'HttpClientTimeout' , [ false , 'HTTP connection and receive timeout' ] ) ,
50- OptBool . new ( 'HttpPartialResponses' , [ false , 'Return partial HTTP responses despite timeouts' , false ] ) ,
5152 OptBool . new ( 'HttpTrace' , [ false , 'Show the raw HTTP requests and responses' , false ] ) ,
5253 OptBool . new ( 'HttpTraceHeadersOnly' , [ false , 'Show HTTP headers only in HttpTrace' , false ] ) ,
5354 OptString . new ( 'HttpTraceColors' , [ false , 'HTTP request and response colors for HttpTrace (unset to disable)' , 'red/blu' ] )
@@ -88,6 +89,9 @@ def initialize(info = {})
8889 )
8990 register_autofilter_ports ( [ 80 , 8080 , 443 , 8000 , 8888 , 8880 , 8008 , 3000 , 8443 ] )
9091 register_autofilter_services ( %W{ http https } )
92+
93+ # Initialize an empty cookie jar to keep cookies
94+ self . cookie_jar = Set . new
9195 end
9296
9397 def deregister_http_client_options
@@ -168,7 +172,7 @@ def connect(opts={})
168172 nclient . set_config (
169173 'vhost' => opts [ 'vhost' ] || opts [ 'rhost' ] || self . vhost ( ) ,
170174 'agent' => datastore [ 'UserAgent' ] ,
171- 'partial' => opts [ 'partial' ] || datastore [ 'HttpPartialResponses' ] ,
175+ 'partial' => opts [ 'partial' ] ,
172176 'uri_encode_mode' => datastore [ 'HTTP::uri_encode_mode' ] ,
173177 'uri_full_url' => datastore [ 'HTTP::uri_full_url' ] ,
174178 'pad_method_uri_count' => datastore [ 'HTTP::pad_method_uri_count' ] ,
@@ -314,69 +318,80 @@ def cleanup
314318 #
315319 # Passes +opts+ through directly to Rex::Proto::Http::Client#request_raw.
316320 #
317- def send_request_raw ( opts = { } , timeout = 20 , disconnect = false )
321+ def send_request_raw ( opts = { } , timeout = 20 , disconnect = false )
318322 if datastore [ 'HttpClientTimeout' ] && datastore [ 'HttpClientTimeout' ] > 0
319323 actual_timeout = datastore [ 'HttpClientTimeout' ]
320324 else
321- actual_timeout = opts [ :timeout ] || timeout
325+ actual_timeout = opts [ :timeout ] || timeout
322326 end
323327
324- begin
325- c = connect ( opts )
326- r = opts [ :cgi ] ? c . request_cgi ( opts ) : c . request_raw ( opts )
328+ c = connect ( opts )
329+ r = opts [ :cgi ] ? c . request_cgi ( opts ) : c . request_raw ( opts )
327330
328- if datastore [ 'HttpTrace' ]
329- request_color , response_color =
330- ( datastore [ 'HttpTraceColors' ] || '' ) . split ( '/' ) . map { |color | "%bld%#{ color } " }
331+ if datastore [ 'HttpTrace' ]
332+ request_color , response_color =
333+ ( datastore [ 'HttpTraceColors' ] || '' ) . split ( '/' ) . map { |color | "%bld%#{ color } " }
331334
332- request = r . to_s ( headers_only : datastore [ 'HttpTraceHeaders' ] )
335+ request = r . to_s ( headers_only : datastore [ 'HttpTraceHeaders' ] )
333336
334- print_line ( '#' * 20 )
335- print_line ( '# Request:' )
336- print_line ( '#' * 20 )
337- print_line ( "%clr#{ request_color } #{ request } %clr" )
338- end
337+ print_line ( '#' * 20 )
338+ print_line ( '# Request:' )
339+ print_line ( '#' * 20 )
340+ print_line ( "%clr#{ request_color } #{ request } %clr" )
341+ end
339342
340- res = c . send_recv ( r , actual_timeout )
343+ res = c . send_recv ( r , actual_timeout )
341344
342- if datastore [ 'HttpTrace' ]
343- print_line ( '#' * 20 )
344- print_line ( '# Response:' )
345- print_line ( '#' * 20 )
345+ if datastore [ 'HttpTrace' ]
346+ print_line ( '#' * 20 )
347+ print_line ( '# Response:' )
348+ print_line ( '#' * 20 )
346349
347- if res
348- response = res . to_terminal_output ( headers_only : datastore [ 'HttpTraceHeadersOnly' ] )
350+ if res
351+ response = res . to_terminal_output ( headers_only : datastore [ 'HttpTraceHeadersOnly' ] )
349352
350- print_line ( "%clr#{ response_color } #{ response } %clr" )
351- else
352- print_line ( 'No response received' )
353- end
353+ print_line ( "%clr#{ response_color } #{ response } %clr" )
354+ else
355+ print_line ( 'No response received' )
354356 end
357+ end
355358
356- disconnect ( c ) if disconnect
359+ disconnect ( c ) if disconnect
357360
358- res
359- rescue ::Errno ::EPIPE , ::Timeout ::Error => e
360- print_line ( e . message ) if datastore [ 'HttpTrace' ]
361- nil
362- rescue Rex ::ConnectionError => e
363- vprint_error ( e . to_s )
364- nil
365- rescue ::Exception => e
366- print_line ( e . message ) if datastore [ 'HttpTrace' ]
367- raise e
368- end
361+ res
362+ rescue ::Errno ::EPIPE , ::Timeout ::Error => e
363+ print_line ( e . message ) if datastore [ 'HttpTrace' ]
364+ nil
365+ rescue Rex ::ConnectionError => e
366+ vprint_error ( e . to_s )
367+ nil
368+ rescue ::Exception => e
369+ print_line ( e . message ) if datastore [ 'HttpTrace' ]
370+ raise e
369371 end
370372
371-
372373 # Connects to the server, creates a request, sends the request,
373374 # reads the response
374375 #
375376 # Passes `opts` through directly to {Rex::Proto::Http::Client#request_cgi}.
377+ # Set `opts['keep_cookies']` to keep cookies from responses for reuse in requests.
376378 #
377379 # @return (see Rex::Proto::Http::Client#send_recv))
378- def send_request_cgi ( opts = { } , timeout = 20 , disconnect = true )
379- send_request_raw ( opts . merge ( cgi : true ) , timeout , disconnect )
380+ def send_request_cgi ( opts = { } , timeout = 20 , disconnect = true )
381+ if cookie_jar . any?
382+ opts = { 'cookie' => cookie_jar . to_a . join ( ' ' ) } . merge ( opts )
383+ end
384+
385+ res = send_request_raw ( opts . merge ( cgi : true ) , timeout , disconnect )
386+
387+ return unless res
388+
389+ if opts [ 'keep_cookies' ] && res . headers [ 'Set-Cookie' ] . present?
390+ # XXX: CGI::Cookie (get_cookies_parsed) is hella broken
391+ cookie_jar . merge ( res . get_cookies . split ( ' ' ) )
392+ end
393+
394+ res
380395 end
381396
382397 # Connects to the server, creates a request, sends the request, reads the
@@ -387,30 +402,31 @@ def send_request_cgi(opts={}, timeout = 20, disconnect = true)
387402 # `opts['redirect_uri']` will contain the full URI.
388403 #
389404 # @return (see #send_request_cgi)
390- def send_request_cgi! ( opts = { } , timeout = 20 , redirect_depth = 1 )
391- if datastore [ 'HttpClientTimeout' ] && datastore [ 'HttpClientTimeout' ] > 0
392- actual_timeout = datastore [ 'HttpClientTimeout' ]
393- else
394- actual_timeout = opts [ :timeout ] || timeout
395- end
405+ def send_request_cgi! ( opts = { } , timeout = 20 , redirect_depth = 1 )
406+ res = send_request_cgi ( opts , timeout )
396407
397- res = send_request_cgi ( opts , actual_timeout )
398- return res unless res && res . redirect? && redirect_depth > 0
408+ return unless res
409+ return res unless res . redirect? && res . redirection && redirect_depth > 0
399410
400411 redirect_depth -= 1
401- return res if res . redirection . nil?
402412
403413 reconfig_redirect_opts! ( res , opts )
404- send_request_cgi! ( opts , actual_timeout , redirect_depth )
414+ send_request_cgi! ( opts , timeout , redirect_depth )
405415 end
406416
407-
408417 # Modifies the HTTP request options for a redirection.
409418 #
410419 # @param res [Rex::Proto::HTTP::Response] HTTP Response.
411420 # @param opts [Hash] The HTTP request options to modify.
412421 # @return [void]
413422 def reconfig_redirect_opts! ( res , opts )
423+ # XXX: https://github.com/rapid7/metasploit-framework/issues/12281
424+ if opts [ 'method' ] == 'POST'
425+ opts [ 'method' ] = 'GET'
426+ opts [ 'data' ] = nil
427+ opts [ 'vars_post' ] = { }
428+ end
429+
414430 location = res . redirection
415431
416432 if location . relative?
@@ -425,7 +441,7 @@ def reconfig_redirect_opts!(res, opts)
425441 opts [ 'redirect_uri' ] = new_redirect_uri
426442 opts [ 'uri' ] = new_redirect_uri
427443 end
428-
444+
429445 opts [ 'rhost' ] = datastore [ 'RHOST' ]
430446 opts [ 'vhost' ] = opts [ 'vhost' ] || opts [ 'rhost' ] || self . vhost ( )
431447 opts [ 'rport' ] = datastore [ 'RPORT' ]
@@ -446,6 +462,9 @@ def reconfig_redirect_opts!(res, opts)
446462 opts [ 'SSL' ] = false
447463 end
448464 end
465+
466+ # Don't forget any GET parameters
467+ opts [ 'query' ] ||= location . query if location . query
449468 end
450469
451470 #
@@ -867,10 +886,10 @@ def service_details
867886 }
868887 end
869888
870- protected
889+ protected
871890
872891 attr_accessor :client
892+ attr_accessor :cookie_jar
873893
874894end
875-
876895end
0 commit comments