2012-06-29 00:18:28 -05:00
# -*- coding: binary -*-
2016-04-25 14:30:46 -05:00
2012-03-09 14:44:32 -06:00
require 'uri'
2011-03-14 22:37:16 +00:00
require 'digest'
2010-08-07 06:59:16 +00:00
module Msf
###
#
# This module provides methods for acting as an HTTP client when
# exploiting an HTTP server.
#
###
module Exploit::Remote::HttpClient
2013-08-30 16:28:33 -05:00
include Msf :: Auxiliary :: Report
#
# Initializes an exploit module that exploits a vulnerability in an HTTP
# server.
#
def initialize ( info = { } )
super
register_options (
[
Opt :: RHOST ,
Opt :: RPORT ( 80 ) ,
OptString . new ( 'VHOST' , [ false , " HTTP server virtual host " ] ) ,
2016-01-22 07:38:44 +01:00
OptBool . new ( 'SSL' , [ false , 'Negotiate SSL/TLS for outgoing connections' , false ] ) ,
2013-08-30 16:28:33 -05:00
Opt :: Proxies
] , self . class
)
register_advanced_options (
[
OptString . new ( 'UserAgent' , [ false , 'The User-Agent header to use for all requests' ,
Rex :: Proto :: Http :: Client :: DefaultUserAgent
] ) ,
2016-05-27 18:31:54 -05:00
OptString . new ( 'HttpUsername' , [ false , 'The HTTP username to specify for authentication' , '' ] ) ,
OptString . new ( 'HttpPassword' , [ false , 'The HTTP password to specify for authentication' , '' ] ) ,
2019-07-24 21:23:22 -05:00
OptPath . new ( 'HttpRawHeaders' , [ false , 'Path to ERB-templatized raw headers to append to existing headers' ] ) ,
2013-08-30 16:28:33 -05:00
OptBool . new ( 'DigestAuthIIS' , [ false , 'Conform to IIS, should work for most servers. Only set to false for non-IIS servers' , true ] ) ,
2016-03-06 22:06:27 -06:00
Opt :: SSLVersion ,
2013-08-30 16:28:33 -05:00
OptBool . new ( 'FingerprintCheck' , [ false , 'Conduct a pre-exploit fingerprint verification' , true ] ) ,
2015-05-20 13:30:45 -05:00
OptString . new ( 'DOMAIN' , [ true , 'The domain to use for windows authentification' , 'WORKSTATION' ] ) ,
2020-01-13 22:25:18 -06:00
OptFloat . new ( 'HttpClientTimeout' , [ false , 'HTTP connection and receive timeout' ] ) ,
2019-10-31 11:00:02 -05:00
OptBool . new ( 'HttpPartialResponses' , [ false , 'Return partial HTTP responses despite timeouts' , false ] ) ,
2016-08-04 16:09:17 -05:00
OptBool . new ( 'HttpTrace' , [ false , 'Show the raw HTTP requests and responses' , false ] )
2013-08-30 16:28:33 -05:00
] , self . class
)
register_evasion_options (
[
2014-05-12 11:26:27 -05:00
OptEnum . new ( 'HTTP::uri_encode_mode' , [ false , 'Enable URI encoding' , 'hex-normal' , [ 'none' , 'hex-normal' , 'hex-noslashes' , 'hex-random' , 'hex-all' , 'u-normal' , 'u-all' , 'u-random' ] ] ) ,
2013-08-30 16:28:33 -05:00
OptBool . new ( 'HTTP::uri_full_url' , [ false , 'Use the full URL for all HTTP requests' , false ] ) ,
OptInt . new ( 'HTTP::pad_method_uri_count' , [ false , 'How many whitespace characters to use between the method and uri' , 1 ] ) ,
OptInt . new ( 'HTTP::pad_uri_version_count' , [ false , 'How many whitespace characters to use between the uri and version' , 1 ] ) ,
OptEnum . new ( 'HTTP::pad_method_uri_type' , [ false , 'What type of whitespace to use between the method and uri' , 'space' , [ 'space' , 'tab' , 'apache' ] ] ) ,
OptEnum . new ( 'HTTP::pad_uri_version_type' , [ false , 'What type of whitespace to use between the uri and version' , 'space' , [ 'space' , 'tab' , 'apache' ] ] ) ,
OptBool . new ( 'HTTP::method_random_valid' , [ false , 'Use a random, but valid, HTTP method for request' , false ] ) ,
OptBool . new ( 'HTTP::method_random_invalid' , [ false , 'Use a random invalid, HTTP method for request' , false ] ) ,
OptBool . new ( 'HTTP::method_random_case' , [ false , 'Use random casing for the HTTP method' , false ] ) ,
2015-08-20 10:05:42 -07:00
OptBool . new ( 'HTTP::version_random_valid' , [ false , 'Use a random, but valid, HTTP version for request' , false ] ) ,
OptBool . new ( 'HTTP::version_random_invalid' , [ false , 'Use a random invalid, HTTP version for request' , false ] ) ,
2013-08-30 16:28:33 -05:00
OptBool . new ( 'HTTP::uri_dir_self_reference' , [ false , 'Insert self-referential directories into the uri' , false ] ) ,
OptBool . new ( 'HTTP::uri_dir_fake_relative' , [ false , 'Insert fake relative directories into the uri' , false ] ) ,
OptBool . new ( 'HTTP::uri_use_backslashes' , [ false , 'Use back slashes instead of forward slashes in the uri ' , false ] ) ,
OptBool . new ( 'HTTP::pad_fake_headers' , [ false , 'Insert random, fake headers into the HTTP request' , false ] ) ,
OptInt . new ( 'HTTP::pad_fake_headers_count' , [ false , 'How many fake headers to insert into the HTTP request' , 0 ] ) ,
OptBool . new ( 'HTTP::pad_get_params' , [ false , 'Insert random, fake query string variables into the request' , false ] ) ,
OptInt . new ( 'HTTP::pad_get_params_count' , [ false , 'How many fake query string variables to insert into the request' , 16 ] ) ,
OptBool . new ( 'HTTP::pad_post_params' , [ false , 'Insert random, fake post variables into the request' , false ] ) ,
OptInt . new ( 'HTTP::pad_post_params_count' , [ false , 'How many fake post variables to insert into the request' , 16 ] ) ,
OptBool . new ( 'HTTP::uri_fake_end' , [ false , 'Add a fake end of URI (eg: /%20HTTP/1.0/../../)' , false ] ) ,
OptBool . new ( 'HTTP::uri_fake_params_start' , [ false , 'Add a fake start of params to the URI (eg: /%3fa=b/../)' , false ] ) ,
OptBool . new ( 'HTTP::header_folding' , [ false , 'Enable folding of HTTP headers' , false ] )
2010-08-07 06:59:16 +00:00
#
# Remaining evasions to implement
#
2016-03-15 18:35:32 -05:00
# OptBool.new('HTTP::chunked', [false, 'Enable chunking of HTTP request via "Transfer-Encoding: chunked"', false]),
2014-10-15 14:21:39 -05:00
# OptInt.new('HTTP::junk_pipeline', [true, 'Insert the specified number of junk pipeline requests', 0]),
2013-09-05 14:11:03 -05:00
] , self . class
)
register_autofilter_ports ( [ 80 , 8080 , 443 , 8000 , 8888 , 8880 , 8008 , 3000 , 8443 ] )
register_autofilter_services ( %W{ http https } )
end
2019-03-05 04:43:03 -06:00
def deregister_http_client_options
deregister_options ( 'RHOST' , 'RPORT' , 'VHOST' , 'SSL' , 'Proxies' )
end
2013-09-05 14:11:03 -05:00
#
# For HTTP Client exploits, we often want to verify that the server info matches some regex before
# firing a giant binary exploit blob at it. We override setup() here to accomplish that.
#
def setup
validate_fingerprint
super
end
#
# This method is meant to be overriden in the exploit module to specify a set of regexps to
# attempt to match against. A failure to match any of them results in a RuntimeError exception
# being raised.
#
def validate_fingerprint ( )
# Don't bother checking if there's no database active.
if ( framework . db . active and
datastore [ 'FingerprintCheck' ] and
self . class . const_defined? ( 'HttpFingerprint' ) )
# Get the module-specific config
opts = self . class . const_get ( 'HttpFingerprint' )
#
# XXX: Ideally we could have more structured matches, but doing that requires
# a more structured response cache.
#
info = http_fingerprint ( opts )
if info and opts [ :pattern ]
opts [ :pattern ] . each do | re |
if not re . match ( info )
err = " The target server fingerprint \" #{ info } \" does not match \" #{ re . to_s } \" , use 'set FingerprintCheck false' to disable this check. "
2013-11-27 11:19:46 -06:00
fail_with ( :: Msf :: Module :: Failure :: NotFound , err )
2013-09-05 14:11:03 -05:00
end
end
2018-07-25 21:11:10 -05:00
elsif info . nil?
err = " The target server did not respond to fingerprinting, use 'set FingerprintCheck false' to disable this check. "
fail_with ( :: Msf :: Module :: Failure :: Unreachable , err )
2013-09-05 14:11:03 -05:00
end
end
end
#
# Connects to an HTTP server.
#
def connect ( opts = { } )
dossl = false
if ( opts . has_key? ( 'SSL' ) )
dossl = opts [ 'SSL' ]
else
dossl = ssl
end
2016-05-27 18:31:54 -05:00
client_username = opts [ 'username' ] || datastore [ 'HttpUsername' ] || ''
client_password = opts [ 'password' ] || datastore [ 'HttpPassword' ] || ''
2013-09-05 14:11:03 -05:00
nclient = Rex :: Proto :: Http :: Client . new (
opts [ 'rhost' ] || rhost ,
( opts [ 'rport' ] || rport ) . to_i ,
{
'Msf' = > framework ,
'MsfExploit' = > self ,
} ,
dossl ,
ssl_version ,
proxies ,
client_username ,
client_password
)
# Configure the HTTP client with the supplied parameter
nclient . set_config (
2014-04-10 16:47:49 -05:00
'vhost' = > opts [ 'vhost' ] || opts [ 'rhost' ] || self . vhost ( ) ,
2013-09-05 14:11:03 -05:00
'agent' = > datastore [ 'UserAgent' ] ,
2019-10-31 11:00:02 -05:00
'partial' = > opts [ 'partial' ] || datastore [ 'HttpPartialResponses' ] ,
2013-09-05 14:11:03 -05:00
'uri_encode_mode' = > datastore [ 'HTTP::uri_encode_mode' ] ,
'uri_full_url' = > datastore [ 'HTTP::uri_full_url' ] ,
'pad_method_uri_count' = > datastore [ 'HTTP::pad_method_uri_count' ] ,
'pad_uri_version_count' = > datastore [ 'HTTP::pad_uri_version_count' ] ,
'pad_method_uri_type' = > datastore [ 'HTTP::pad_method_uri_type' ] ,
'pad_uri_version_type' = > datastore [ 'HTTP::pad_uri_version_type' ] ,
'method_random_valid' = > datastore [ 'HTTP::method_random_valid' ] ,
'method_random_invalid' = > datastore [ 'HTTP::method_random_invalid' ] ,
'method_random_case' = > datastore [ 'HTTP::method_random_case' ] ,
2015-08-20 10:05:42 -07:00
'version_random_valid' = > datastore [ 'HTTP::version_random_valid' ] ,
'version_random_invalid' = > datastore [ 'HTTP::version_random_invalid' ] ,
2013-09-05 14:11:03 -05:00
'uri_dir_self_reference' = > datastore [ 'HTTP::uri_dir_self_reference' ] ,
'uri_dir_fake_relative' = > datastore [ 'HTTP::uri_dir_fake_relative' ] ,
'uri_use_backslashes' = > datastore [ 'HTTP::uri_use_backslashes' ] ,
'pad_fake_headers' = > datastore [ 'HTTP::pad_fake_headers' ] ,
'pad_fake_headers_count' = > datastore [ 'HTTP::pad_fake_headers_count' ] ,
'pad_get_params' = > datastore [ 'HTTP::pad_get_params' ] ,
'pad_get_params_count' = > datastore [ 'HTTP::pad_get_params_count' ] ,
'pad_post_params' = > datastore [ 'HTTP::pad_post_params' ] ,
'pad_post_params_count' = > datastore [ 'HTTP::pad_post_params_count' ] ,
'uri_fake_end' = > datastore [ 'HTTP::uri_fake_end' ] ,
'uri_fake_params_start' = > datastore [ 'HTTP::uri_fake_params_start' ] ,
'header_folding' = > datastore [ 'HTTP::header_folding' ] ,
2013-11-20 01:03:13 -06:00
'domain' = > datastore [ 'DOMAIN' ] ,
'DigestAuthIIS' = > datastore [ 'DigestAuthIIS' ]
2013-09-05 14:11:03 -05:00
)
2019-10-31 12:23:26 -05:00
# NOTE: Please use opts['headers'] to programmatically set headers
2019-07-24 21:23:22 -05:00
if datastore [ 'HttpRawHeaders' ] && File . readable? ( datastore [ 'HttpRawHeaders' ] )
# Templatize with ERB
headers = ERB . new ( File . read ( datastore [ 'HttpRawHeaders' ] ) ) . result ( binding )
# Append templatized headers to existing headers
nclient . set_config ( 'raw_headers' = > headers )
2019-07-24 12:56:32 -05:00
end
2013-09-05 14:11:03 -05:00
# If this connection is global, persist it
# Required for findsock on these sockets
if ( opts [ 'global' ] )
if ( self . client )
disconnect
end
end
2016-08-15 15:45:52 -05:00
self . client = nclient
2013-09-05 14:11:03 -05:00
return nclient
end
2015-02-22 02:26:15 -06:00
#
# Converts datastore options into configuration parameters for the
# Metasploit::LoginScanner::Http class. Any parameters passed into
# this method will override the defaults.
#
def configure_http_login_scanner ( conf )
{
host : rhost ,
port : rport ,
2015-02-22 02:45:50 -06:00
ssl : ssl ,
2015-02-22 02:26:15 -06:00
ssl_version : ssl_version ,
proxies : datastore [ 'PROXIES' ] ,
framework : framework ,
framework_module : self ,
vhost : vhost ,
user_agent : datastore [ 'UserAgent' ] ,
evade_uri_encode_mode : datastore [ 'HTTP::uri_encode_mode' ] ,
evade_uri_full_url : datastore [ 'HTTP::uri_full_url' ] ,
evade_pad_method_uri_count : datastore [ 'HTTP::pad_method_uri_count' ] ,
evade_pad_uri_version_count : datastore [ 'HTTP::pad_uri_version_count' ] ,
evade_pad_method_uri_type : datastore [ 'HTTP::pad_method_uri_type' ] ,
evade_pad_uri_version_type : datastore [ 'HTTP::pad_uri_version_type' ] ,
evade_method_random_valid : datastore [ 'HTTP::method_random_valid' ] ,
evade_method_random_invalid : datastore [ 'HTTP::method_random_invalid' ] ,
evade_method_random_case : datastore [ 'HTTP::method_random_case' ] ,
2015-08-20 10:05:42 -07:00
evade_version_random_valid : datastore [ 'HTTP::version_random_valid' ] ,
evade_version_random_invalid : datastore [ 'HTTP::version_random_invalid' ] ,
2015-02-22 02:26:15 -06:00
evade_uri_dir_self_reference : datastore [ 'HTTP::uri_dir_self_reference' ] ,
evade_uri_dir_fake_relative : datastore [ 'HTTP::uri_dir_fake_relative' ] ,
evade_uri_use_backslashes : datastore [ 'HTTP::uri_use_backslashes' ] ,
evade_pad_fake_headers : datastore [ 'HTTP::pad_fake_headers' ] ,
evade_pad_fake_headers_count : datastore [ 'HTTP::pad_fake_headers_count' ] ,
evade_pad_get_params : datastore [ 'HTTP::pad_get_params' ] ,
evade_pad_get_params_count : datastore [ 'HTTP::pad_get_params_count' ] ,
evade_pad_post_params : datastore [ 'HTTP::pad_post_params' ] ,
evade_pad_post_params_count : datastore [ 'HTTP::pad_post_params_count' ] ,
evade_uri_fake_end : datastore [ 'HTTP::uri_fake_end' ] ,
evade_uri_fake_params_start : datastore [ 'HTTP::uri_fake_params_start' ] ,
evade_header_folding : datastore [ 'HTTP::header_folding' ] ,
ntlm_domain : datastore [ 'DOMAIN' ] ,
digest_auth_iis : datastore [ 'DigestAuthIIS' ]
} . merge ( conf )
end
2013-09-05 14:11:03 -05:00
#
# Passes the client connection down to the handler to see if it's of any
# use.
#
def handler ( nsock = nil )
# If no socket was provided, try the global one.
if ( ( ! nsock ) and ( self . client ) )
nsock = self . client . conn
end
# If the parent claims the socket associated with the HTTP client, then
# we rip the socket out from under the HTTP client.
if ( ( ( rv = super ( nsock ) ) == Handler :: Claimed ) and
( self . client ) and
( nsock == self . client . conn ) )
self . client . conn = nil
end
rv
end
#
# Disconnects the HTTP client
#
def disconnect ( nclient = self . client )
if ( nclient )
nclient . close
end
if ( nclient == self . client )
2016-08-15 15:45:52 -05:00
if self . client . respond_to? ( :close )
self . client . close
end
2013-09-05 14:11:03 -05:00
self . client = nil
end
end
#
# Performs cleanup as necessary, disconnecting the HTTP client if it's
# still established.
#
def cleanup
super
disconnect
end
#
# Connects to the server, creates a request, sends the request, reads the response
#
# Passes +opts+ through directly to Rex::Proto::Http::Client#request_raw.
#
2019-10-31 11:00:02 -05:00
def send_request_raw ( opts = { } , timeout = 20 , disconnect = false )
2015-07-20 11:27:48 -05:00
if datastore [ 'HttpClientTimeout' ] && datastore [ 'HttpClientTimeout' ] > 0
actual_timeout = datastore [ 'HttpClientTimeout' ]
else
actual_timeout = opts [ :timeout ] || timeout
end
2013-09-05 14:11:03 -05:00
begin
c = connect ( opts )
r = c . request_raw ( opts )
2016-08-05 13:20:53 -05:00
if datastore [ 'HttpTrace' ]
print_line ( '#' * 20 )
print_line ( '# Request:' )
print_line ( '#' * 20 )
print_line ( r . to_s )
end
2019-10-31 11:00:02 -05:00
res = c . send_recv ( r , actual_timeout )
2016-08-05 13:20:53 -05:00
if datastore [ 'HttpTrace' ]
print_line ( '#' * 20 )
print_line ( '# Response:' )
print_line ( '#' * 20 )
2018-05-26 10:04:26 +02:00
if res . nil?
print_line ( " No response received " )
else
print_line ( res . to_terminal_output )
end
2016-08-05 13:20:53 -05:00
end
2019-10-31 11:00:02 -05:00
disconnect ( c ) if disconnect
2019-10-29 21:24:39 -05:00
2016-08-04 16:09:17 -05:00
res
2016-08-04 18:14:22 -05:00
rescue :: Errno :: EPIPE , :: Timeout :: Error = > e
print_line ( e . message ) if datastore [ 'HttpTrace' ]
2013-09-05 14:11:03 -05:00
nil
2018-07-12 23:51:41 -04:00
rescue Rex :: ConnectionError = > e
vprint_error ( e . to_s )
nil
2016-08-04 18:14:22 -05:00
rescue :: Exception = > e
2016-08-05 14:12:50 -05:00
print_line ( e . message ) if datastore [ 'HttpTrace' ]
2016-08-04 18:14:22 -05:00
raise e
2013-09-05 14:11:03 -05:00
end
end
2014-02-03 21:49:49 +00:00
# Connects to the server, creates a request, sends the request,
# reads the response
2013-09-05 14:11:03 -05:00
#
2016-08-18 10:40:32 -05:00
# Passes `opts` through directly to {Rex::Proto::Http::Client#request_cgi}.
2013-09-05 14:11:03 -05:00
#
2016-08-18 10:40:32 -05:00
# @return (see Rex::Proto::Http::Client#send_recv))
2019-10-31 11:00:02 -05:00
def send_request_cgi ( opts = { } , timeout = 20 , disconnect = true )
2015-07-20 11:27:48 -05:00
if datastore [ 'HttpClientTimeout' ] && datastore [ 'HttpClientTimeout' ] > 0
actual_timeout = datastore [ 'HttpClientTimeout' ]
else
actual_timeout = opts [ :timeout ] || timeout
end
2016-08-05 13:20:53 -05:00
print_line ( " * " * 20 ) if datastore [ 'HttpTrace' ]
2013-09-05 14:11:03 -05:00
begin
c = connect ( opts )
r = c . request_cgi ( opts )
2016-08-05 13:20:53 -05:00
if datastore [ 'HttpTrace' ]
print_line ( '#' * 20 )
print_line ( '# Request:' )
print_line ( '#' * 20 )
print_line ( r . to_s )
end
2019-10-31 11:00:02 -05:00
res = c . send_recv ( r , actual_timeout )
2016-08-05 13:20:53 -05:00
if datastore [ 'HttpTrace' ]
print_line ( '#' * 20 )
print_line ( '# Response:' )
print_line ( '#' * 20 )
2018-05-26 10:04:26 +02:00
if res . nil?
print_line ( " No response received " )
else
print_line ( res . to_terminal_output )
end
2016-08-05 13:20:53 -05:00
end
2019-10-29 21:24:39 -05:00
2019-10-31 11:00:02 -05:00
disconnect ( c ) if disconnect
2019-10-29 21:24:39 -05:00
2016-08-04 16:09:17 -05:00
res
2016-08-04 18:14:22 -05:00
rescue :: Errno :: EPIPE , :: Timeout :: Error = > e
print_line ( e . message ) if datastore [ 'HttpTrace' ]
2014-02-03 21:49:49 +00:00
nil
2018-07-12 23:51:18 -04:00
rescue Rex :: ConnectionError = > e
vprint_error ( e . to_s )
nil
2016-08-04 18:14:22 -05:00
rescue :: Exception = > e
2016-08-05 14:12:50 -05:00
print_line ( e . message ) if datastore [ 'HttpTrace' ]
2016-08-04 18:14:22 -05:00
raise e
2014-02-03 21:49:49 +00:00
end
end
2014-02-02 15:03:44 +00:00
2016-08-18 10:40:32 -05:00
# Connects to the server, creates a request, sends the request, reads the
# response if a redirect (HTTP 30x response) is received it will attempt to
# follow the direct and retrieve that URI.
2014-02-03 21:49:49 +00:00
#
2016-08-18 10:40:32 -05:00
# @note `opts` will be updated to the updated location and
# `opts['redirect_uri']` will contain the full URI.
2014-02-03 21:49:49 +00:00
#
2016-08-18 10:40:32 -05:00
# @return (see #send_request_cgi)
2014-02-04 15:25:51 +00:00
def send_request_cgi! ( opts = { } , timeout = 20 , redirect_depth = 1 )
2015-07-20 11:27:48 -05:00
if datastore [ 'HttpClientTimeout' ] && datastore [ 'HttpClientTimeout' ] > 0
actual_timeout = datastore [ 'HttpClientTimeout' ]
else
actual_timeout = opts [ :timeout ] || timeout
end
2015-05-20 13:30:45 -05:00
res = send_request_cgi ( opts , actual_timeout )
2014-02-10 08:57:09 -06:00
return res unless res && res . redirect? && redirect_depth > 0
2014-02-02 16:07:46 +00:00
2014-02-10 08:57:09 -06:00
redirect_depth -= 1
2016-04-25 14:30:46 -05:00
return res if res . redirection . nil?
reconfig_redirect_opts! ( res , opts )
send_request_cgi! ( opts , actual_timeout , redirect_depth )
end
2014-02-02 16:07:46 +00:00
2016-04-25 14:30:46 -05:00
# Modifies the HTTP request options for a redirection.
#
# @param res [Rex::Proto::HTTP::Response] HTTP Response.
# @param opts [Hash] The HTTP request options to modify.
# @return [void]
def reconfig_redirect_opts! ( res , opts )
location = res . redirection
if location . relative?
parent_path = File . dirname ( opts [ 'uri' ] . to_s )
parent_path = '/' if parent_path == '.'
new_redirect_uri = normalize_uri ( parent_path , location . path . gsub ( / ^ \ . / , '' ) )
opts [ 'redirect_uri' ] = new_redirect_uri
opts [ 'uri' ] = new_redirect_uri
opts [ 'rhost' ] = datastore [ 'RHOST' ]
opts [ 'vhost' ] = opts [ 'vhost' ] || opts [ 'rhost' ] || self . vhost ( )
opts [ 'rport' ] = datastore [ 'RPORT' ]
2019-08-20 04:03:10 -05:00
opts [ 'SSL' ] = ssl
2014-02-10 23:41:15 +00:00
else
2019-08-06 15:25:34 -05:00
disconnect
2016-04-25 14:30:46 -05:00
opts [ 'redirect_uri' ] = location
opts [ 'uri' ] = location . path
opts [ 'rhost' ] = location . host
opts [ 'vhost' ] = location . host
opts [ 'rport' ] = location . port
if location . scheme == 'https'
2019-08-06 15:25:34 -05:00
opts [ 'SSL' ] = true
2016-04-25 14:30:46 -05:00
else
2019-08-06 15:25:34 -05:00
opts [ 'SSL' ] = false
2016-04-25 14:30:46 -05:00
end
2013-09-05 14:11:03 -05:00
end
end
#
# Combine the user/pass into an auth string for the HTTP Client
#
def basic_auth ( username , password )
auth_str = Rex :: Text . encode_base64 ( " #{ username } : #{ password } " )
" Basic #{ auth_str } "
end
##
#
# Wrappers for getters
#
##
#
# Returns the target URI
#
def target_uri
begin
# In case TARGETURI is empty, at least we default to '/'
u = datastore [ 'TARGETURI' ]
u = " / " if u . nil? or u . empty?
URI ( u )
rescue :: URI :: InvalidURIError
print_error " Invalid URI: #{ datastore [ 'TARGETURI' ] . inspect } "
raise Msf :: OptionValidateError . new ( [ 'TARGETURI' ] )
end
end
2013-11-07 23:07:58 +01:00
# Returns the complete URI as string including the scheme, port and host
2019-02-25 14:45:26 -06:00
def full_uri ( custom_uri = nil , vhost_uri : false )
2013-09-29 19:34:44 +02:00
uri_scheme = ssl ? 'https' : 'http'
2017-02-08 09:39:47 -06:00
if ( rport == 80 && ! ssl ) || ( rport == 443 && ssl )
uri_port = ''
else
uri_port = " : #{ rport } "
end
2017-02-03 04:39:27 -06:00
uri = normalize_uri ( custom_uri || target_uri . to_s )
2017-02-08 09:39:47 -06:00
2019-02-25 14:45:26 -06:00
if vhost_uri && datastore [ 'VHOST' ]
uri_host = datastore [ 'VHOST' ]
elsif Rex :: Socket . is_ipv6? ( rhost )
2017-12-07 12:56:55 -06:00
uri_host = " [ #{ rhost } ] "
else
uri_host = rhost
end
" #{ uri_scheme } :// #{ uri_host } #{ uri_port } #{ uri } "
2013-09-29 19:34:44 +02:00
end
2013-09-05 14:11:03 -05:00
#
# Returns a modified version of the URI that:
# 1. Always has a starting slash
# 2. Removes all the double slashes
#
def normalize_uri ( * strs )
new_str = strs * " / "
new_str = new_str . gsub! ( " // " , " / " ) while new_str . index ( " // " )
# Makes sure there's a starting slash
unless new_str [ 0 , 1 ] == '/'
new_str = '/' + new_str
end
new_str
end
# Returns the Path+Query from a full URI String, nil on error
def path_from_uri ( uri )
begin
temp = URI ( uri )
ret_uri = temp . path
ret_uri << " ? #{ temp . query } " unless temp . query . nil? or temp . query . empty?
return ret_uri
rescue URI :: Error
print_error " Invalid URI: #{ uri } "
return nil
end
end
2017-07-10 04:19:26 -04:00
#
# Returns a hash of request opts from a URL string
def request_opts_from_url ( url )
2017-08-14 01:08:53 -04:00
# verify and extract components from the URL
begin
tgt = URI . parse ( url )
raise 'Invalid URL' unless tgt . scheme =~ %r{ https? }
raise 'Invalid URL' if tgt . host . to_s . eql? ''
rescue = > e
print_error " Could not parse URL: #{ e } "
return nil
end
2017-07-10 04:19:26 -04:00
opts = { 'rhost' = > tgt . host , 'rport' = > tgt . port , 'uri' = > tgt . request_uri }
opts [ 'SSL' ] = true if tgt . scheme == 'https'
if tgt . query and tgt . query . size > 13
# Assming that this is going to be mostly used for GET requests as string -> req
opts [ 'vars_get' ] = { }
tgt . query . split ( '&' ) . each do | pair |
k , v = pair . split ( '=' , 2 )
opts [ 'vars_get' ] [ k ] = v
end
end
return opts
end
2017-07-11 16:07:13 -04:00
#
# Returns response from a simple URL call
def request_url ( url , keepalive = false )
2017-08-14 01:08:53 -04:00
opts = request_opts_from_url ( url )
return nil if opts . nil?
res = send_request_raw ( opts )
2017-07-11 16:07:13 -04:00
disconnect unless keepalive
return res
end
2017-08-14 01:08:53 -04:00
#
# Downloads a URL
def download ( url )
print_status " Downloading ' #{ url } ' "
begin
target = URI . parse url
raise 'Invalid URL' unless target . scheme =~ / https? /
raise 'Invalid URL' if target . host . to_s . eql? ''
rescue = > e
print_error " Could not parse URL: #{ e } "
return nil
end
res = request_url ( url )
unless res
print_error 'Connection failed'
return nil
end
print_status " - HTTP #{ res . code } - #{ res . body . length } bytes "
res . code == 200 ? res . body : nil
end
2013-09-29 19:34:44 +02:00
# removes HTML tags from a provided string.
# The string is html-unescaped before the tags are removed
# Leading whitespaces and double linebreaks are removed too
def strip_tags ( html )
Rex :: Text . html_decode ( html ) . gsub ( / < \/ ?[^>]*> / , '' ) . gsub ( / ^ \ s+ / , '' ) . strip
end
2013-09-05 14:11:03 -05:00
#
# Returns the target host
#
def rhost
datastore [ 'RHOST' ]
end
#
# Returns the remote port
#
def rport
datastore [ 'RPORT' ]
end
#
# Returns the Host and Port as a string
#
def peer
" #{ rhost } : #{ rport } "
end
#
# Returns the VHOST of the HTTP server.
#
def vhost
datastore [ 'VHOST' ] || datastore [ 'RHOST' ]
end
#
# Returns the boolean indicating SSL
#
def ssl
( ( datastore . default? ( 'SSL' ) and [ 443 , 3790 ] . include? ( rport . to_i ) ) or datastore [ 'SSL' ] )
end
#
# Returns the string indicating SSL version
#
def ssl_version
datastore [ 'SSLVersion' ]
end
#
# Returns the configured proxy list
#
def proxies
datastore [ 'Proxies' ]
end
2014-03-23 07:26:11 -07:00
#
# Lookup HTTP fingerprints from the database that match the current
# destination host and port. This method falls back to using the old
# service.info field to represent the HTTP Server header.
#
2014-08-24 14:19:39 -05:00
# @option opts [String] :uri ('/') An HTTP URI to request in order to generate
# a fingerprint
# @option opts [String] :method ('GET') An HTTP method to use in the fingerprint
# request
2014-03-23 07:26:11 -07:00
def lookup_http_fingerprints ( opts = { } )
uri = opts [ :uri ] || '/'
method = opts [ :method ] || 'GET'
fprints = [ ]
2014-08-24 14:19:39 -05:00
2014-03-23 07:26:11 -07:00
return fprints unless framework . db . active
2014-08-24 14:19:39 -05:00
2014-03-23 07:26:11 -07:00
:: ActiveRecord :: Base . connection_pool . with_connection {
2014-08-25 01:45:59 -05:00
wspace = datastore [ 'WORKSPACE' ] ?
2014-03-23 07:26:11 -07:00
framework . db . find_workspace ( datastore [ 'WORKSPACE' ] ) : framework . db . workspace
service = framework . db . get_service ( wspace , rhost , 'tcp' , rport )
return fprints unless service
2014-08-24 14:19:39 -05:00
2014-03-23 07:26:11 -07:00
# Order by note_id descending so the first value is the most recent
service . notes . where ( :ntype = > 'http.fingerprint' ) . order ( " notes.id DESC " ) . each do | n |
2014-08-24 14:19:39 -05:00
next unless n . data && n . data . kind_of? ( :: Hash )
next unless n . data [ :uri ] == uri && n . data [ :method ] == method
2014-03-23 07:26:11 -07:00
# Append additional fingerprints to the results as found
fprints . unshift n . data . dup
end
}
fprints
end
2013-09-05 14:11:03 -05:00
#
# Record various things about an HTTP server that we can glean from the
# response to a single request. If this method is passed a response, it
# will use it directly, otherwise it will check the database for a previous
# fingerprint. Failing that, it will make a request for /.
#
2014-08-24 14:19:39 -05:00
# Other options are passed directly to {#connect} if :response is not given
2013-09-05 14:11:03 -05:00
#
2014-08-24 14:19:39 -05:00
# @option opts [Rex::Proto::Http::Packet] :response The return value from any
# of the send_* methods
# @option opts [String] :uri ('/') An HTTP URI to request in order to generate
# a fingerprint
# @option opts [String] :method ('GET') An HTTP method to use in the fingerprint
# request
# @option opts [Boolean] :full (false) Request the full HTTP fingerprint, not
# just the signature
2013-09-05 14:11:03 -05:00
#
2014-08-24 14:19:39 -05:00
# @return [String]
2013-09-05 14:11:03 -05:00
def http_fingerprint ( opts = { } )
2014-03-23 07:26:11 -07:00
res = nil
uri = opts [ :uri ] || '/'
method = opts [ :method ] || 'GET'
2013-09-05 14:11:03 -05:00
2014-03-23 07:26:11 -07:00
# Short-circuit the fingerprint lookup and HTTP request if a response has
# already been provided by the caller.
if opts [ :response ]
2013-09-05 14:11:03 -05:00
res = opts [ :response ]
else
2014-03-23 07:26:11 -07:00
fprints = lookup_http_fingerprints ( opts )
2013-09-05 14:11:03 -05:00
2014-03-23 07:26:11 -07:00
if fprints . length > 0
2014-08-24 14:19:39 -05:00
2014-03-23 07:26:11 -07:00
# Grab the most recent fingerprint available for this service, uri, and method
fprint = fprints . last
# Return the full HTTP fingerprint if requested by the caller
return fprint if opts [ :full ]
# Otherwise just return the signature string for compatibility
2014-03-23 10:42:20 -07:00
return fprint [ :signature ]
2013-09-05 14:11:03 -05:00
end
2014-03-23 07:26:11 -07:00
# Go ahead and send a request to the target for fingerprinting
2013-09-05 14:11:03 -05:00
connect ( opts )
res = send_request_raw (
{
'uri' = > uri ,
'method' = > method
2018-07-25 21:39:02 -05:00
} ) rescue nil
2013-09-05 14:11:03 -05:00
end
2014-03-23 07:26:11 -07:00
# Bail if the request did not receive a readable response
2013-09-05 14:11:03 -05:00
return if not res
2014-03-23 07:26:11 -07:00
# This section handles a few simple cases of pattern matching and service
# classification. This logic should be deprecated in favor of Recog-based
# fingerprint databases, but has been left in place for backward compat.
2014-08-24 14:19:39 -05:00
2013-09-05 14:11:03 -05:00
extras = [ ]
2014-08-24 14:19:39 -05:00
2013-09-05 14:11:03 -05:00
if res . headers [ 'Set-Cookie' ] =~ / ^vmware_soap_session /
extras << " VMWare Web Services "
end
if ( res . headers [ 'X-Powered-By' ] )
extras << " Powered by " + res . headers [ 'X-Powered-By' ]
end
if ( res . headers [ 'Via' ] )
extras << " Via- " + res . headers [ 'Via' ]
end
if ( res . headers [ 'X-AspNet-Version' ] )
extras << " AspNet-Version- " + res . headers [ 'X-AspNet-Version' ]
end
case res . body
when nil
# Nothing
when / openAboutWindow.* \ >DD \ -WRT ([^ \ <]+) \ <|Authorization.*please note that the default username is \ "root \ " in /
extras << " DD-WRT #{ $1 . to_s . strip } " . strip
when / ID_ESX_Welcome / , / ID_ESX_VIClientDesc /
extras << " VMware ESX Server "
when / Test Page for.*Fedora /
extras << " Fedora Default Page "
when / Placeholder page /
extras << " Debian Default Page "
when / Welcome to Windows Small Business Server ( \ d+) /
extras << " Windows SBS #{ $1 } "
when / Asterisk@Home /
extras << " Asterisk "
when / swfs \/ Shell \ .html /
extras << " BPS-1000 "
end
if datastore [ 'RPORT' ] . to_i == 3790
2013-09-12 16:58:49 -05:00
if res . code == 302 and res . headers and res . headers [ 'Location' ] =~ / [ \ x5c \ x2f](login|setup)$ /n
if res [ 'Server' ] =~ / ^(thin.*No Hup)|(nginx[ \ x5c \ x2f][ \ d \ .]+)$ /n
2013-09-05 14:11:03 -05:00
extras << " Metasploit "
end
end
end
2014-03-23 07:26:11 -07:00
#
# This HTTP response code tracking is used by a few modules and the MSP logic
# to identify and bruteforce certain types of servers. In the long run we
# should deprecate this and use the http.fingerprint fields instead.
#
2013-09-05 14:11:03 -05:00
case res . code
when 301 , 302
extras << " #{ res . code } - #{ res . headers [ 'Location' ] } "
when 401
extras << " #{ res . code } - #{ res . headers [ 'WWW-Authenticate' ] } "
when 403
extras << " #{ res . code } - #{ res . headers [ 'WWW-Authenticate' ] || res . message } "
when 500 .. 599
extras << " #{ res . code } - #{ res . message } "
end
2014-03-23 07:26:11 -07:00
# Build a human-readable string to store in service.info and http.fingerprint[:signature]
info = res . headers [ 'Server' ] . to_s . dup
2013-09-05 14:11:03 -05:00
info << " ( #{ extras . join ( " , " ) } ) " if extras . length > 0
2014-03-23 07:26:11 -07:00
# Create a new fingerprint structure to track this response
2014-08-24 14:19:39 -05:00
fprint = {
2016-08-24 13:11:03 -05:00
:uri = > uri , :method = > method , :server_port = > rport ,
2014-03-23 07:26:11 -07:00
:code = > res . code . to_s , :message = > res . message . to_s ,
:signature = > info
}
res . headers . each_pair do | k , v |
2018-09-17 09:45:52 -04:00
hname = k . to_s . downcase . tr ( '-' , '_' ) . gsub ( / [^a-z0-9_]+ / , '' )
2014-03-23 07:26:11 -07:00
next unless hname . length > 0
# Set-Cookie > :header_set_cookie => JSESSIONID=AAASD23423452
# Server > :header_server => Apache/1.3.37
# WWW-Authenticate > :header_www_authenticate => basic realm='www'
2014-08-24 14:19:39 -05:00
2014-03-23 07:26:11 -07:00
fprint [ " header_ #{ hname } " . intern ] = v
end
# Store the first 64k of the HTTP body as well
fprint [ :content ] = res . body . to_s [ 0 , 65535 ]
# Report a new http.fingerprint note
2014-03-23 10:42:20 -07:00
report_note (
2014-08-24 14:19:39 -05:00
:host = > rhost ,
:port = > rport ,
:proto = > 'tcp' ,
:ntype = > 'http.fingerprint' ,
2014-03-23 10:42:20 -07:00
:data = > fprint ,
2014-08-24 14:19:39 -05:00
# Limit reporting to one stored note per host/service combination
2014-03-23 10:42:20 -07:00
:update = > :unique
)
2014-03-23 07:26:11 -07:00
2013-09-05 14:11:03 -05:00
# Report here even if info is empty since the fact that we didn't
# return early means we at least got a connection and the service is up
report_web_site ( :host = > rhost , :port = > rport , :ssl = > ssl , :vhost = > vhost , :info = > info . dup )
2014-08-24 14:19:39 -05:00
2014-03-23 07:26:11 -07:00
# Return the full HTTP fingerprint if requested by the caller
return fprint if opts [ :full ]
2014-08-24 14:19:39 -05:00
2014-03-23 07:26:11 -07:00
# Otherwise just return the signature string for compatibility
fprint [ :signature ]
2013-09-05 14:11:03 -05:00
end
2017-05-01 15:52:53 -05:00
def service_details
{
origin_type : :service ,
protocol : 'tcp' ,
service_name : ( ssl ? 'https' : 'http' ) ,
address : rhost ,
port : rport
}
end
2010-08-07 06:59:16 +00:00
protected
2013-08-30 16:28:33 -05:00
attr_accessor :client
2010-08-07 06:59:16 +00:00
end
2013-02-11 20:49:55 -06:00
end