Files
metasploit-gs/lib/msf/core/exploit/http/client.rb
T
wchen-r7 47d52a250e Fix #6806 and #6820 - Fix send_request_cgi! redirection
This patch fixes two problems:

1. 6820 - If the HTTP server returns a relative path
   (example: /test), there is no host to extract, therefore the HOST
   header in the HTTP request ends up being empty. When the web
   server sees this, it might return an HTTP 400 Bad Request, and
   the redirection fails.

2. 6806 - If the HTTP server returns a relative path that begins
   with a dot, send_request_cgi! will literally send that in the
   GET request. Since that isn't a valid GET request path format,
   the redirection fails.

Fix #6806
Fix #6820
2016-04-25 14:30:46 -05:00

758 lines
26 KiB
Ruby

# -*- coding: binary -*-
require 'uri'
require 'digest'
require 'rex/proto/ntlm/crypt'
require 'rex/proto/ntlm/constants'
require 'rex/proto/ntlm/utils'
require 'rex/proto/ntlm/exceptions'
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
include Exploit::Remote::NTLM::Client
#
# Constants
#
NTLM_CRYPT = Rex::Proto::NTLM::Crypt
NTLM_CONST = Rex::Proto::NTLM::Constants
NTLM_UTILS = Rex::Proto::NTLM::Utils
NTLM_XCEPT = Rex::Proto::NTLM::Exceptions
#
# 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" ]),
OptBool.new('SSL', [ false, 'Negotiate SSL/TLS for outgoing connections', false]),
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
]),
OptString.new('USERNAME', [false, 'The HTTP username to specify for authentication', '']),
OptString.new('PASSWORD', [false, 'The HTTP password to specify for authentication', '']),
OptBool.new('DigestAuthIIS', [false, 'Conform to IIS, should work for most servers. Only set to false for non-IIS servers', true]),
Opt::SSLVersion,
OptBool.new('FingerprintCheck', [ false, 'Conduct a pre-exploit fingerprint verification', true]),
OptString.new('DOMAIN', [ true, 'The domain to use for windows authentification', 'WORKSTATION']),
OptInt.new('HttpClientTimeout', [false, 'HTTP connection and receive timeout'])
], self.class
)
register_evasion_options(
[
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']]),
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::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]),
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 })
# Used by digest auth
@cnonce = make_cnonce
@nonce_count = -1
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.
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."
fail_with(::Msf::Module::Failure::NotFound, err)
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
client_username = opts['username'] || datastore['USERNAME'] || ''
client_password = opts['password'] || datastore['PASSWORD'] || ''
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(
'vhost' => opts['vhost'] || opts['rhost'] || self.vhost(),
'agent' => datastore['UserAgent'],
'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'],
'version_random_valid' => datastore['HTTP::version_random_valid'],
'version_random_invalid' => datastore['HTTP::version_random_invalid'],
'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'],
'usentlm2_session' => datastore['NTLM::UseNTLM2_session'],
'use_ntlmv2' => datastore['NTLM::UseNTLMv2'],
'send_lm' => datastore['NTLM::SendLM'],
'send_ntlm' => datastore['NTLM::SendNTLM'],
'SendSPN' => datastore['NTLM::SendSPN'],
'UseLMKey' => datastore['NTLM::UseLMKey'],
'domain' => datastore['DOMAIN'],
'DigestAuthIIS' => datastore['DigestAuthIIS']
)
# 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
#
# 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,
ssl: ssl,
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'],
evade_version_random_valid: datastore['HTTP::version_random_valid'],
evade_version_random_invalid: datastore['HTTP::version_random_invalid'],
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_use_ntlmv2_session: datastore['NTLM::UseNTLM2_session'],
ntlm_use_ntlmv2: datastore['NTLM::UseNTLMv2'],
ntlm_send_lm: datastore['NTLM::SendLM'],
ntlm_send_ntlm: datastore['NTLM::SendNTLM'],
ntlm_send_spn: datastore['NTLM::SendSPN'],
ntlm_use_lm_key: datastore['NTLM::UseLMKey'],
ntlm_domain: datastore['DOMAIN'],
digest_auth_iis: datastore['DigestAuthIIS']
}.merge(conf)
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.
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)
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.
#
def send_request_raw(opts={}, timeout = 20)
if datastore['HttpClientTimeout'] && datastore['HttpClientTimeout'] > 0
actual_timeout = datastore['HttpClientTimeout']
else
actual_timeout = opts[:timeout] || timeout
end
begin
c = connect(opts)
r = c.request_raw(opts)
c.send_recv(r, actual_timeout)
rescue ::Errno::EPIPE, ::Timeout::Error
nil
end
end
# Connects to the server, creates a request, sends the request,
# reads the response
#
# Passes +opts+ through directly to Rex::Proto::Http::Client#request_cgi.
#
def send_request_cgi(opts={}, timeout = 20)
if datastore['HttpClientTimeout'] && datastore['HttpClientTimeout'] > 0
actual_timeout = datastore['HttpClientTimeout']
else
actual_timeout = opts[:timeout] || timeout
end
begin
c = connect(opts)
r = c.request_cgi(opts)
c.send_recv(r, actual_timeout)
rescue ::Errno::EPIPE, ::Timeout::Error
nil
end
end
#
# 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.
#
# @note The +opts+ will be updated to the updated location and +opts['redirect_uri']+
# will contain the full URI.
#
def send_request_cgi!(opts={}, timeout = 20, redirect_depth = 1)
if datastore['HttpClientTimeout'] && datastore['HttpClientTimeout'] > 0
actual_timeout = datastore['HttpClientTimeout']
else
actual_timeout = opts[:timeout] || timeout
end
res = send_request_cgi(opts, actual_timeout)
return res unless res && res.redirect? && redirect_depth > 0
redirect_depth -= 1
return res if res.redirection.nil?
reconfig_redirect_opts!(res, opts)
send_request_cgi!(opts, actual_timeout, redirect_depth)
end
# 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']
opts['ssl'] = ssl
else
opts['redirect_uri'] = location
opts['uri'] = location.path
opts['rhost'] = location.host
opts['vhost'] = location.host
opts['rport'] = location.port
if location.scheme == 'https'
opts['ssl'] = true
else
opts['ssl'] = false
end
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
# Returns the complete URI as string including the scheme, port and host
def full_uri
uri_scheme = ssl ? 'https' : 'http'
uri_port = rport.to_s == '80' ? '' : ":#{rport}"
uri = normalize_uri(target_uri.to_s)
"#{uri_scheme}://#{rhost}#{uri_port}#{uri}"
end
#
# 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
# 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
#
# 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
#
# 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.
#
# @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
def lookup_http_fingerprints(opts={})
uri = opts[:uri] || '/'
method = opts[:method] || 'GET'
fprints = []
return fprints unless framework.db.active
::ActiveRecord::Base.connection_pool.with_connection {
wspace = datastore['WORKSPACE'] ?
framework.db.find_workspace(datastore['WORKSPACE']) : framework.db.workspace
service = framework.db.get_service(wspace, rhost, 'tcp', rport)
return fprints unless service
# 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|
next unless n.data && n.data.kind_of?(::Hash)
next unless n.data[:uri] == uri && n.data[:method] == method
# Append additional fingerprints to the results as found
fprints.unshift n.data.dup
end
}
fprints
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 /.
#
# Other options are passed directly to {#connect} if :response is not given
#
# @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
#
# @return [String]
def http_fingerprint(opts={})
res = nil
uri = opts[:uri] || '/'
method = opts[:method] || 'GET'
# Short-circuit the fingerprint lookup and HTTP request if a response has
# already been provided by the caller.
if opts[:response]
res = opts[:response]
else
fprints = lookup_http_fingerprints(opts)
if fprints.length > 0
# 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
return fprint[:signature]
end
# Go ahead and send a request to the target for fingerprinting
connect(opts)
res = send_request_raw(
{
'uri' => uri,
'method' => method
})
end
# Bail if the request did not receive a readable response
return if not res
# 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.
extras = []
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
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
extras << "Metasploit"
end
end
end
#
# 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.
#
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
# Build a human-readable string to store in service.info and http.fingerprint[:signature]
info = res.headers['Server'].to_s.dup
info << " ( #{extras.join(", ")} )" if extras.length > 0
# Create a new fingerprint structure to track this response
fprint = {
:uri => uri, :method => method,
:code => res.code.to_s, :message => res.message.to_s,
:signature => info
}
res.headers.each_pair do |k,v|
hname = k.to_s.downcase.gsub('-', '_').gsub(/[^a-z0-9_]+/, '')
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'
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
report_note(
:host => rhost,
:port => rport,
:proto => 'tcp',
:ntype => 'http.fingerprint',
:data => fprint,
# Limit reporting to one stored note per host/service combination
:update => :unique
)
# 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)
# Return the full HTTP fingerprint if requested by the caller
return fprint if opts[:full]
# Otherwise just return the signature string for compatibility
fprint[:signature]
end
def make_cnonce
Digest::MD5.hexdigest "%x" % (Time.now.to_i + rand(65535))
end
protected
attr_accessor :client
end
end