2012-06-29 00:18:28 -05:00
# -*- coding: binary -*-
2012-03-09 14:44:32 -06:00
require 'uri'
2011-03-14 22:37:16 +00:00
require 'digest'
2011-05-12 20:03:55 +00:00
require 'rex/proto/ntlm/crypt'
require 'rex/proto/ntlm/constants'
require 'rex/proto/ntlm/utils'
require 'rex/proto/ntlm/exceptions'
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
include Msf :: Auxiliary :: Report
2011-04-03 20:38:27 +00:00
include Exploit :: Remote :: NTLM :: Client
#
# Constants
#
2011-10-24 04:58:59 +00:00
NTLM_CRYPT = Rex :: Proto :: NTLM :: Crypt
2011-04-03 20:38:27 +00:00
NTLM_CONST = Rex :: Proto :: NTLM :: Constants
NTLM_UTILS = Rex :: Proto :: NTLM :: Utils
NTLM_XCEPT = Rex :: Proto :: NTLM :: Exceptions
2010-08-07 06:59:16 +00:00
#
# 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 " ] ) ,
Opt :: Proxies
] , self . class
)
register_advanced_options (
[
2011-07-28 22:57:47 +00:00
OptString . new ( 'UserAgent' , [ false , 'The User-Agent header to use for all requests' ,
Rex :: Proto :: Http :: Client :: DefaultUserAgent
] ) ,
2010-08-07 06:59:16 +00:00
OptString . new ( 'BasicAuthUser' , [ false , 'The HTTP username to specify for basic authentication' ] ) ,
OptString . new ( 'BasicAuthPass' , [ false , 'The HTTP password to specify for basic authentication' ] ) ,
2011-03-14 22:37:16 +00:00
OptString . new ( 'DigestAuthUser' , [ false , 'The HTTP username to specify for digest authentication' ] ) ,
OptString . new ( 'DigestAuthPassword' , [ false , 'The HTTP password to specify for digest authentication' ] ) ,
OptBool . new ( 'DigestAuthIIS' , [ false , 'Conform to IIS, should work for most servers. Only set to false for non-IIS servers' , true ] ) ,
2010-08-07 06:59:16 +00:00
OptBool . new ( 'SSL' , [ false , 'Negotiate SSL for outgoing connections' , false ] ) ,
OptEnum . new ( 'SSLVersion' , [ false , 'Specify the version of SSL that should be used' , 'SSL3' , [ 'SSL2' , 'SSL3' , 'TLS1' ] ] ) ,
2011-04-03 20:38:27 +00:00
OptBool . new ( 'FingerprintCheck' , [ false , 'Conduct a pre-exploit fingerprint verification' , true ] ) ,
OptString . new ( 'DOMAIN' , [ true , 'The domain to use for windows authentification' , 'WORKSTATION' ] )
2010-08-07 06:59:16 +00:00
] , self . class
)
register_evasion_options (
[
OptEnum . new ( 'HTTP::uri_encode_mode' , [ false , 'Enable URI encoding' , 'hex-normal' , [ 'none' , 'hex-normal' , 'hex-all' , 'hex-random' , 'u-normal' , 'u-all' , 'u-random' ] ] ) ,
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 ] ) ,
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 ] )
#
# Remaining evasions to implement
#
# OptBool.new('HTTP::chunked', [false, 'Enable chunking of HTTP request via "Transfer-Encoding: chunked"', 'false']),
# OptInt.new('HTTP::junk_pipeline', [true, 'Insert the specified number of junk pipeline requests', 0]),
] , self . class
)
register_autofilter_ports ( [ 80 , 8080 , 443 , 8000 , 8888 , 8880 , 8008 , 3000 , 8443 ] )
register_autofilter_services ( %W{ http https } )
2011-03-14 22:37:16 +00:00
# Used by digest auth
@cnonce = make_cnonce
@nonce_count = - 1
2010-08-07 06:59:16 +00:00
end
#
# 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.
2010-09-26 21:02:00 +00:00
if ( framework . db . active and
datastore [ 'FingerprintCheck' ] and
2010-08-07 06:59:16 +00:00
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. "
2012-06-10 03:15:48 -05:00
fail_with ( Msf :: Exploit :: Failure :: NotFound , err )
2010-08-07 06:59:16 +00:00
end
end
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
nclient = Rex :: Proto :: Http :: Client . new (
rhost ,
rport . to_i ,
{
'Msf' = > framework ,
'MsfExploit' = > self ,
} ,
dossl ,
ssl_version ,
proxies
)
# Configure the HTTP client with the supplied parameter
nclient . set_config (
'vhost' = > self . vhost ( ) ,
'agent' = > datastore [ 'UserAgent' ] ,
'basic_auth' = > self . basic_auth ,
'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' ] ,
'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' ]
)
# If this connection is global, persist it
# Required for findsock on these sockets
if ( opts [ 'global' ] )
if ( self . client )
disconnect
end
self . client = nclient
end
return nclient
end
#
# 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.
2010-09-26 21:02:00 +00:00
if ( ( ! nsock ) and ( self . client ) )
2010-08-07 06:59:16 +00:00
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
2010-09-26 21:02:00 +00:00
( self . client ) and
( nsock == self . client . conn ) )
2010-08-07 06:59:16 +00:00
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 )
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
#
2010-09-11 17:31:26 +00:00
# Passes +opts+ through directly to Rex::Proto::Http::Client#request_raw.
#
2010-08-07 06:59:16 +00:00
def send_request_raw ( opts = { } , timeout = 20 )
begin
c = connect ( opts )
r = c . request_raw ( opts )
2010-11-05 16:20:13 +00:00
c . send_recv ( r , opts [ :timeout ] ? opts [ :timeout ] : timeout )
2010-08-07 06:59:16 +00:00
rescue :: Errno :: EPIPE , :: Timeout :: Error
nil
end
end
#
# Connects to the server, creates a request, sends the request, reads the response
#
2010-09-11 17:31:26 +00:00
# Passes +opts+ through directly to Rex::Proto::Http::Client#request_cgi.
#
2010-08-07 06:59:16 +00:00
def send_request_cgi ( opts = { } , timeout = 20 )
begin
c = connect ( opts )
r = c . request_cgi ( opts )
2010-11-05 16:20:13 +00:00
c . send_recv ( r , opts [ :timeout ] ? opts [ :timeout ] : timeout )
2010-08-07 06:59:16 +00:00
rescue :: Errno :: EPIPE , :: Timeout :: Error
nil
end
end
#
# Combine the user/pass into an auth string for the HTTP Client
#
def basic_auth
return if not datastore [ 'BasicAuthUser' ]
datastore [ 'BasicAuthUser' ] + " : " + ( datastore [ 'BasicAuthPass' ] || '' )
end
#
# Connect to the server, and perform NTLM authentication for this session.
# Note the return value is [resp,c], so the caller can have access to both
# the last response, and the connection itself -- this is important since
# NTLM auth is bound to this particular TCP session.
#
# TODO: Fix up error messaging a lot more -- right now it's pretty hard
# to tell what all went wrong.
#
def send_http_auth_ntlm ( opts = { } , timeout = 20 )
2011-04-03 20:38:27 +00:00
#ntlm_message_1 = "NTLM TlRMTVNTUAABAAAAB4IIAAAAAAAAAAAAAAAAAAAAAAA="
ntlm_options = {
:signing = > false ,
:usentlm2_session = > datastore [ 'NTLM::UseNTLM2_session' ] ,
:use_ntlmv2 = > datastore [ 'NTLM::UseNTLMv2' ] ,
:send_lm = > datastore [ 'NTLM::SendLM' ] ,
:send_ntlm = > datastore [ 'NTLM::SendNTLM' ]
}
ntlmssp_flags = NTLM_UTILS . make_ntlm_flags ( ntlm_options )
workstation_name = Rex :: Text . rand_text_alpha ( rand ( 8 ) + 1 )
2011-10-24 04:58:59 +00:00
domain_name = datastore [ 'DOMAIN' ]
2011-04-03 20:38:27 +00:00
2011-10-24 04:58:59 +00:00
ntlm_message_1 = " NTLM " + Rex :: Text :: encode_base64 ( NTLM_UTILS :: make_ntlmssp_blob_init ( domain_name ,
workstation_name ,
2011-04-03 20:38:27 +00:00
ntlmssp_flags ) )
2010-08-07 06:59:16 +00:00
to = opts [ :timeout ] || timeout
begin
c = connect ( opts )
# First request to get the challenge
r = c . request_cgi ( opts . merge ( {
'uri' = > opts [ 'uri' ] ,
'method' = > 'GET' ,
'headers' = > { 'Authorization' = > ntlm_message_1 } } ) )
resp = c . send_recv ( r , to )
unless resp . kind_of? Rex :: Proto :: Http :: Response
return [ nil , nil ]
end
return [ nil , nil ] if resp . code == 404
return [ nil , nil ] unless resp . code == 401 && resp . headers [ 'WWW-Authenticate' ]
# Get the challenge and craft the response
ntlm_challenge = resp . headers [ 'WWW-Authenticate' ] . match ( / NTLM ([A-Z0-9 \ x2b \ x2f=]+) /i ) [ 1 ]
return [ nil , nil ] unless ntlm_challenge
2011-04-03 20:38:27 +00:00
2011-10-24 04:58:59 +00:00
#old and simplier method but not compatible with windows 7/2008r2
2011-04-03 20:38:27 +00:00
#ntlm_message_2 = Rex::Proto::NTLM::Message.decode64(ntlm_challenge)
#ntlm_message_3 = ntlm_message_2.response( {:user => opts['username'],:password => opts['password']}, {:ntlmv2 => true})
ntlm_message_2 = Rex :: Text :: decode_base64 ( ntlm_challenge )
blob_data = NTLM_UTILS . parse_ntlm_type_2_blob ( ntlm_message_2 )
challenge_key = blob_data [ :challenge_key ]
server_ntlmssp_flags = blob_data [ :server_ntlmssp_flags ] #else should raise an error
#netbios name
default_name = blob_data [ :default_name ] || ''
#netbios domain
default_domain = blob_data [ :default_domain ] || ''
#dns name
dns_host_name = blob_data [ :dns_host_name ] || ''
#dns domain
dns_domain_name = blob_data [ :dns_domain_name ] || ''
#Client time
chall_MsvAvTimestamp = blob_data [ :chall_MsvAvTimestamp ] || ''
2011-10-24 04:58:59 +00:00
2011-04-03 20:38:27 +00:00
spnopt = { :use_spn = > datastore [ 'NTLM::SendSPN' ] , :name = > self . rhost }
2011-10-24 04:58:59 +00:00
resp_lm ,
resp_ntlm ,
client_challenge ,
2011-04-03 20:38:27 +00:00
ntlm_cli_challenge = NTLM_UTILS . create_lm_ntlm_responses ( opts [ 'username' ] , opts [ 'password' ] , challenge_key ,
domain_name , default_name , default_domain ,
dns_host_name , dns_domain_name , chall_MsvAvTimestamp ,
spnopt , ntlm_options )
2011-10-24 04:58:59 +00:00
ntlm_message_3 = NTLM_UTILS . make_ntlmssp_blob_auth ( domain_name , workstation_name , opts [ 'username' ] ,
2011-04-03 20:38:27 +00:00
resp_lm , resp_ntlm , '' , ntlmssp_flags )
ntlm_message_3 = Rex :: Text :: encode_base64 ( ntlm_message_3 )
2010-08-07 06:59:16 +00:00
# Send the response
r = c . request_cgi ( opts . merge ( {
'uri' = > opts [ 'uri' ] ,
'method' = > 'GET' ,
2011-04-03 20:38:27 +00:00
'headers' = > { 'Authorization' = > " NTLM #{ ntlm_message_3 } " } } ) )
2010-08-07 06:59:16 +00:00
resp = c . send_recv ( r , to , true )
unless resp . kind_of? Rex :: Proto :: Http :: Response
return [ nil , nil ]
end
return [ nil , nil ] if resp . code == 404
return [ resp , c ]
rescue :: Errno :: EPIPE , :: Timeout :: Error
end
end
2011-10-24 04:58:59 +00:00
2011-03-14 22:37:16 +00:00
def send_digest_request_cgi ( opts = { } , timeout = 20 )
@nonce_count = 0
2011-10-24 04:58:59 +00:00
2011-03-14 22:37:16 +00:00
return [ nil , nil ] if not ( datastore [ 'DigestAuthUser' ] or opts [ 'DigestAuthUser' ] )
to = opts [ 'timeout' ] || timeout
2011-10-24 04:58:59 +00:00
2011-03-14 22:37:16 +00:00
digest_user = datastore [ 'DigestAuthUser' ] || opts [ 'DigestAuthUser' ] || " "
digest_password = datastore [ 'DigestAuthPassword' ] || opts [ 'DigestAuthPassword' ] || " "
2011-10-24 04:58:59 +00:00
2011-03-14 22:37:16 +00:00
method = opts [ 'method' ]
path = opts [ 'uri' ]
iis = true
if ( opts [ 'DigestAuthIIS' ] == false or datastore [ 'DigestAuthIIS' ] == false )
iis = false
end
2011-10-24 04:58:59 +00:00
2011-03-14 22:37:16 +00:00
begin
@nonce_count += 1
resp = opts [ 'response' ]
if not resp
# Get authentication-challenge from server, and read out parameters required
c = connect ( opts )
r = c . request_cgi ( opts . merge ( {
'uri' = > path ,
'method' = > method } ) )
resp = c . send_recv ( r , to )
unless resp . kind_of? Rex :: Proto :: Http :: Response
return [ nil , nil ]
end
return [ nil , nil ] if resp . code == 404
if resp . code != 401
return resp
end
return [ nil , nil ] unless resp . headers [ 'WWW-Authenticate' ]
end
# Don't anchor this regex to the beginning of string because header
# folding makes it appear later when the server presents multiple
# WWW-Authentication options (such as is the case with IIS configured
# for Digest or NTLM).
resp [ 'www-authenticate' ] =~ / Digest (.*) /
parameters = { }
$1 . split ( / ,[[:space:]]* / ) . each do | p |
k , v = p . split ( " = " , 2 )
parameters [ k ] = v . gsub ( '"' , '' )
end
qop = parameters [ 'qop' ]
if parameters [ 'algorithm' ] =~ / (.*?)(-sess)?$ /
algorithm = case $1
when 'MD5' then Digest :: MD5
when 'SHA1' then Digest :: SHA1
when 'SHA2' then Digest :: SHA2
when 'SHA256' then Digest :: SHA256
when 'SHA384' then Digest :: SHA384
when 'SHA512' then Digest :: SHA512
when 'RMD160' then Digest :: RMD160
else raise Error , " unknown algorithm \" #{ $1 } \" "
2011-10-24 04:58:59 +00:00
end
2011-03-14 22:37:16 +00:00
algstr = parameters [ " algorithm " ]
sess = $2
else
algorithm = Digest :: MD5
algstr = " MD5 "
sess = false
2011-10-24 04:58:59 +00:00
end
2011-03-14 22:37:16 +00:00
a1 = if sess then
2011-10-24 04:58:59 +00:00
[
2011-03-14 22:37:16 +00:00
algorithm . hexdigest ( " #{ digest_user } : #{ parameters [ 'realm' ] } : #{ digest_password } " ) ,
parameters [ 'nonce' ] ,
@cnonce
] . join ':'
else
" #{ digest_user } : #{ parameters [ 'realm' ] } : #{ digest_password } "
end
ha1 = algorithm . hexdigest ( a1 )
ha2 = algorithm . hexdigest ( " #{ method } : #{ path } " )
request_digest = [ ha1 , parameters [ 'nonce' ] ]
request_digest . push ( ( '%08x' % @nonce_count ) , @cnonce , qop ) if qop
request_digest << ha2
request_digest = request_digest . join ':'
# Same order as IE7
auth = [
" Digest username= \" #{ digest_user } \" " ,
" realm= \" #{ parameters [ 'realm' ] } \" " ,
" nonce= \" #{ parameters [ 'nonce' ] } \" " ,
" uri= \" #{ path } \" " ,
" cnonce= \" #{ @cnonce } \" " ,
" nc= #{ '%08x' % @nonce_count } " ,
" algorithm= #{ algstr } " ,
" response= \" #{ algorithm . hexdigest ( request_digest ) [ 0 , 32 ] } \" " ,
# The spec says the qop value shouldn't be enclosed in quotes, but
# some versions of IIS require it and Apache accepts it. Chrome
# and Firefox both send it without quotes but IE does it this way.
# Use the non-compliant-but-everybody-does-it to be as compatible
# as possible by default. The user can override if they don't like
# it.
if qop . nil? then
elsif iis then
" qop= \" #{ qop } \" "
else
" qop= #{ qop } "
end ,
if parameters . key? 'opaque' then
" opaque= \" #{ parameters [ 'opaque' ] } \" "
end
] . compact
2012-01-05 12:25:32 -08:00
headers = { 'Authorization' = > auth . join ( ', ' ) }
headers . merge! ( opts [ 'headers' ] ) if opts [ 'headers' ]
2012-01-05 12:02:21 -08:00
2011-03-14 22:37:16 +00:00
# Send main request with authentication
r = c . request_cgi ( opts . merge ( {
'uri' = > path ,
'method' = > method ,
2012-01-05 12:02:21 -08:00
'headers' = > headers } ) )
2011-03-14 22:37:16 +00:00
resp = c . send_recv ( r , to )
unless resp . kind_of? Rex :: Proto :: Http :: Response
return [ nil , nil ]
end
2011-10-24 04:58:59 +00:00
2011-03-14 22:37:16 +00:00
return [ resp , c ]
rescue :: Errno :: EPIPE , :: Timeout :: Error
end
end
2010-08-07 06:59:16 +00:00
##
#
# Wrappers for getters
#
##
2012-03-09 14:44:32 -06:00
#
# Returns the target URI
#
def target_uri
2012-03-10 10:58:16 -06:00
begin
2012-04-07 19:43:19 -05:00
# In case TARGETURI is empty, at least we default to '/'
u = datastore [ 'TARGETURI' ]
2012-04-07 19:47:28 -05:00
u = " / " if u . nil? or u . empty?
2012-04-07 19:43:19 -05:00
URI ( u )
2012-03-10 10:58:16 -06:00
rescue :: URI :: InvalidURIError
2012-03-12 13:58:33 -05:00
print_error " Invalid URI: #{ datastore [ 'TARGETURI' ] . inspect } "
raise Msf :: OptionValidateError . new ( [ 'TARGETURI' ] )
2012-03-10 10:58:16 -06:00
end
2012-03-09 14:44:32 -06:00
end
2010-08-07 06:59:16 +00:00
#
# Returns the target host
#
def rhost
datastore [ 'RHOST' ]
end
#
# Returns the remote port
#
def rport
datastore [ 'RPORT' ]
end
#
# Returns the VHOST of the HTTP server.
#
def vhost
datastore [ 'VHOST' ] || datastore [ 'RHOST' ]
end
#
# Returns the boolean indicating SSL
#
def ssl
2012-02-03 12:12:22 -06:00
( ( datastore . default? ( 'SSL' ) and [ 443 , 3790 ] . include? ( rport . to_i ) ) or datastore [ 'SSL' ] )
2010-08-07 06:59:16 +00:00
end
#
# Returns the string indicating SSL version
#
def ssl_version
datastore [ 'SSLVersion' ]
end
#
# Returns the configured proxy list
#
def proxies
datastore [ 'Proxies' ]
end
#
# 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 /.
#
# Options:
# :response an Http::Packet as returned from any of the send_* methods
#
# Other options are passed directly to +connect+ if :response is not given
#
def http_fingerprint ( opts = { } )
if ( opts [ :response ] )
res = opts [ :response ]
else
# Check to see if we already have a fingerprint before going out to
# the network.
if ( framework . db . active )
2012-04-19 23:51:20 -06:00
:: ActiveRecord :: Base . connection_pool . with_connection {
wspace = framework . db . workspace
if datastore [ 'WORKSPACE' ]
wspace = framework . db . find_workspace ( datastore [ 'WORKSPACE' ] )
end
2010-08-07 06:59:16 +00:00
2012-04-19 23:51:20 -06:00
s = framework . db . get_service ( wspace , rhost , 'tcp' , rport )
if ( s and s . info )
return s . info
end
}
2010-08-07 06:59:16 +00:00
end
connect ( opts )
uri = opts [ :uri ] || '/'
method = opts [ :method ] || 'GET'
res = send_request_raw (
{
'uri' = > uri ,
'method' = > method
} )
end
# Bail if we don't have anything to fingerprint
return if not res
2012-02-03 12:12:22 -06:00
2010-08-07 06:59:16 +00:00
# From here to the end simply does some pre-canned combining and custom matches
# to build a human-readable string to store in service.info
extras = [ ]
2012-02-03 12:12:22 -06:00
2012-02-02 22:11:16 -06:00
if res . headers [ 'Set-Cookie' ] =~ / ^vmware_soap_session /
extras << " VMWare Web Services "
end
2010-08-07 06:59:16 +00:00
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
2012-07-15 21:21:13 -05:00
when / openAboutWindow.* \ >DD \ -WRT ([^ \ <]+) \ <|Authorization.*please note that the default username is \ "root \ " in /
extras << " DD-WRT #{ $1 . to_s . strip } " . strip
2010-08-07 06:59:16 +00:00
2012-02-03 12:12:22 -06:00
when / ID_ESX_Welcome / , / ID_ESX_VIClientDesc /
2010-08-07 06:59:16 +00:00
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
2011-10-14 01:32:46 +00:00
if datastore [ 'RPORT' ] . to_i == 3790
2012-02-03 12:12:22 -06:00
if res . code == 302 and res . headers and res . headers [ 'Location' ] =~ / [ \ x5c \ x2f](login|setup)$ /
if res [ 'Server' ] =~ / ^(thin.*No Hup)|(nginx[ \ x5c \ x2f][ \ d \ .]+)$ /
2011-10-14 01:32:46 +00:00
extras << " Metasploit "
end
end
end
2012-02-03 12:12:22 -06: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
2010-08-07 06:59:16 +00:00
info = " #{ res . headers [ 'Server' ] } "
info << " ( #{ extras . join ( " , " ) } ) " if extras . length > 0
# 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
2012-01-26 11:05:06 -06:00
report_web_site ( :host = > rhost , :port = > rport , :ssl = > ssl , :vhost = > vhost , :info = > info . dup )
2010-08-07 06:59:16 +00:00
info
end
2011-10-14 01:32:46 +00:00
2011-03-14 22:37:16 +00:00
def make_cnonce
Digest :: MD5 . hexdigest " %x " % ( Time . now . to_i + rand ( 65535 ) )
end
2010-08-07 06:59:16 +00:00
protected
attr_accessor :client
end
end