Reimplement HTTP fingerprinting, backwards compatible

This commit changes the internals of HTTP fingerprinting to store
a whole trove of data about the HTTP response using a hash. The
current API is backwards compatible and has been tested with a
number of modules that depend on HttpFingerprint being sent.

In addition, this change paves the way for advanced fingerprints
that take advantage of the HTTP body and other headers. This is
a requested addition documented  across various module comments.

Finally, this commit completes the closed loop for OS identification
by connecting MSF to MDM to Recog and applying Recog databases for
HTTP Servers, HTTP Cookies, and HTTP Authentication headers to the
results of HTTP fingerprinting runs.

For example, with the appropriate version of MDM/Recog in place,
a http_version scan of Microsoft-IIS/7.0 server will update the
host.os_name field to 'Windows 2008'.
bug/bundler_fix
HD Moore 2014-03-23 07:26:11 -07:00
parent 506c354722
commit f349f85a70
1 changed files with 103 additions and 23 deletions

View File

@ -439,6 +439,43 @@ module Exploit::Remote::HttpClient
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.
#
# Options:
# :uri an HTTP URI to request in order to generate a fingerprint
# :method 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 and n.data.kind_of?(::Hash)
next unless n.data[:uri] == uri and 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
@ -447,33 +484,38 @@ module Exploit::Remote::HttpClient
#
# Options:
# :response an Http::Packet as returned from any of the send_* methods
# :uri an HTTP URI to request in order to generate a fingerprint
# :method an HTTP method to use in the fingerprint request
# :full request the full HTTP fingerprint, not just the signature
#
# Other options are passed directly to +connect+ if :response is not given
#
def http_fingerprint(opts={})
res = nil
uri = opts[:uri] || '/'
method = opts[:method] || 'GET'
if (opts[:response])
# 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
# Check to see if we already have a fingerprint before going out to
# the network.
if (framework.db.active)
::ActiveRecord::Base.connection_pool.with_connection {
wspace = framework.db.workspace
if datastore['WORKSPACE']
wspace = framework.db.find_workspace(datastore['WORKSPACE'])
end
fprints = lookup_http_fingerprints(opts)
s = framework.db.get_service(wspace, rhost, 'tcp', rport)
if (s and s.info)
return s.info
end
}
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
fprint[:signature]
end
# Go ahead and send a request to the target for fingerprinting
connect(opts)
uri = opts[:uri] || '/'
method = opts[:method] || 'GET'
res = send_request_raw(
{
'uri' => uri,
@ -481,13 +523,15 @@ module Exploit::Remote::HttpClient
})
end
# Bail if we don't have anything to fingerprint
# Bail if the request did not receive a readable response
return if not res
# 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
# 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
@ -537,6 +581,11 @@ module Exploit::Remote::HttpClient
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']}"
@ -548,12 +597,43 @@ module Exploit::Remote::HttpClient
extras << "#{res.code}-#{res.message}"
end
info = "#{res.headers['Server']}"
# 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, :ntype => 'http.fingerprint', :data => fprint, :update => :unique_data)
# 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)
info
# 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
@ -566,4 +646,4 @@ protected
end
end
end