Skip to content

Commit a65ee6c

Browse files
committed
Land rapid7#3373, recog
Conflicts: Gemfile Gemfile.lock data/js/detect/os.js lib/msf/core/exploit/remote/browser_exploit_server.rb modules/exploits/android/browser/webview_addjavascriptinterface.rb
2 parents 097d2bf + 24eec0e commit a65ee6c

File tree

84 files changed

+839
-646
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

84 files changed

+839
-646
lines changed

Gemfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ group :db do
1010
# Metasploit::Credential database models
1111
gem 'metasploit-credential', '~> 0.12.0'
1212
# Database models shared between framework and Pro.
13-
gem 'metasploit_data_models', '~> 0.21.0'
13+
gem 'metasploit_data_models', '~> 0.21.1'
1414
# Needed for module caching in Mdm::ModuleDetails
1515
gem 'pg', '>= 0.11'
1616
end

Gemfile.lock

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ PATH
1414
nokogiri
1515
packetfu (= 1.1.9)
1616
railties
17+
recog (~> 1.0)
1718
robots
1819
rubyzip (~> 1.1)
1920
sqlite3
@@ -111,14 +112,15 @@ GEM
111112
metasploit-model (0.28.0)
112113
activesupport
113114
railties (< 4.0.0)
114-
metasploit_data_models (0.21.0)
115+
metasploit_data_models (0.21.1)
115116
activerecord (>= 3.2.13, < 4.0.0)
116117
activesupport
117118
arel-helpers
118119
metasploit-concern (~> 0.3.0)
119120
metasploit-model (~> 0.28.0)
120121
pg
121122
railties (< 4.0.0)
123+
recog (~> 1.0)
122124
meterpreter_bins (0.0.7)
123125
method_source (0.8.2)
124126
mime-types (1.25.1)
@@ -161,6 +163,8 @@ GEM
161163
rake (10.3.2)
162164
rdoc (3.12.2)
163165
json (~> 1.4)
166+
recog (1.0.0)
167+
nokogiri
164168
redcarpet (3.1.2)
165169
rkelly-remix (0.0.6)
166170
robots (0.10.1)
@@ -220,7 +224,7 @@ DEPENDENCIES
220224
fivemat (= 1.2.1)
221225
metasploit-credential (~> 0.12.0)
222226
metasploit-framework!
223-
metasploit_data_models (~> 0.21.0)
227+
metasploit_data_models (~> 0.21.1)
224228
network_interface (~> 0.0.1)
225229
pcaprub
226230
pg (>= 0.11)

data/js/detect/os.js

Lines changed: 285 additions & 267 deletions
Large diffs are not rendered by default.

documentation/samples/modules/exploits/ie_browser.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class Metasploit4 < Msf::Exploit::Remote
2929
:ua_minver => "8.0",
3030
:ua_maxver => "10.0",
3131
:javascript => true,
32-
:os_name => OperatingSystems::WINDOWS,
32+
:os_name => OperatingSystems::Match::WINDOWS,
3333
:rank => NormalRanking
3434
})
3535

@@ -85,6 +85,8 @@ def get_target(agent)
8585
os_name = 'Windows 7'
8686
when '6.2'
8787
os_name = 'Windows 8'
88+
when '6.3'
89+
os_name = 'Windows 8.1'
8890
end
8991

9092
targets.each do |t|

lib/msf/base/sessions/meterpreter.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,8 @@ def load_session_info()
347347
self.db_record.save!
348348
end
349349

350-
framework.db.update_host_via_sysinfo(:host => self, :workspace => wspace, :info => sysinfo)
350+
# XXX: This is obsolete given the Mdm::Host.normalize_os() support for host.os.session_fingerprint
351+
# framework.db.update_host_via_sysinfo(:host => self, :workspace => wspace, :info => sysinfo)
351352

352353
if nhost
353354
framework.db.report_note({

lib/msf/core/constants.rb

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,32 +58,66 @@ module HttpClients
5858

5959
UNKNOWN = "Unknown"
6060
end
61+
6162
module OperatingSystems
6263
LINUX = "Linux"
6364
MAC_OSX = "Mac OS X"
64-
WINDOWS = "Microsoft Windows"
65+
WINDOWS = "Windows"
6566
FREEBSD = "FreeBSD"
6667
NETBSD = "NetBSD"
6768
OPENBSD = "OpenBSD"
6869
VMWARE = "VMware"
70+
ANDROID = "Android"
71+
APPLE_IOS = "iOS"
6972

7073
module VmwareVersions
7174
ESX = "ESX"
7275
ESXI = "ESXi"
7376
end
7477

7578
module WindowsVersions
79+
NINE5 = "95"
80+
NINE8 = "98"
7681
NT = "NT"
7782
XP = "XP"
7883
TWOK = "2000"
7984
TWOK3 = "2003"
8085
VISTA = "Vista"
8186
TWOK8 = "2008"
87+
TWOK12 = "2012"
8288
SEVEN = "7"
8389
EIGHT = "8"
90+
EIGHTONE = "8.1"
8491
end
8592

8693
UNKNOWN = "Unknown"
94+
95+
module Match
96+
WINDOWS = /^(?:Microsoft )?Windows/
97+
WINDOWS_95 = /^(?:Microsoft )?Windows 95/
98+
WINDOWS_98 = /^(?:Microsoft )?Windows 98/
99+
WINDOWS_ME = /^(?:Microsoft )?Windows ME/
100+
WINDOWS_NT3 = /^(?:Microsoft )?Windows NT 3/
101+
WINDOWS_NT4 = /^(?:Microsoft )?Windows NT 4/
102+
WINDOWS_2000 = /^(?:Microsoft )?Windows 2000/
103+
WINDOWS_XP = /^(?:Microsoft )?Windows XP/
104+
WINDOWS_2003 = /^(?:Microsoft )?Windows 2003/
105+
WINDOWS_VISTA = /^(?:Microsoft )?Windows Vista/
106+
WINDOWS_2008 = /^(?:Microsoft )?Windows 2008/
107+
WINDOWS_7 = /^(?:Microsoft )?Windows 7/
108+
WINDOWS_2012 = /^(?:Microsoft )?Windows 2012/
109+
WINDOWS_8 = /^(?:Microsoft )?Windows 8/
110+
WINDOWS_81 = /^(?:Microsoft )?Windows 8\.1/
111+
112+
LINUX = /^Linux/i
113+
MAC_OSX = /^(?:Apple )?Mac OS X/
114+
FREEBSD = /^FreeBSD/
115+
NETBSD = /^NetBSD/
116+
OPENBSD = /^OpenBSD/
117+
VMWARE = /^VMware/
118+
ANDROID = /^(?:Google )?Android/
119+
APPLE_IOS = /^(?:Apple )?iOS/
120+
end
87121
end
88122
end
89123

@@ -104,5 +138,4 @@ module WindowsVersions
104138
BSD_LICENSE,
105139
ARTISTIC_LICENSE,
106140
UNKNOWN_LICENSE
107-
]
108-
141+
]

lib/msf/core/db.rb

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -304,8 +304,8 @@ def find_or_create_host(opts)
304304
#
305305
# The opts parameter can contain:
306306
# +:state+:: -- one of the Msf::HostState constants
307-
# +:os_name+:: -- one of the Msf::OperatingSystems constants
308-
# +:os_flavor+:: -- something like "XP" or "Gentoo"
307+
# +:os_name+:: -- something like "Windows", "Linux", or "Mac OS X"
308+
# +:os_flavor+:: -- something like "Enterprise", "Pro", or "Home"
309309
# +:os_sp+:: -- something like "SP2"
310310
# +:os_lang+:: -- something like "English", "French", or "en-US"
311311
# +:arch+:: -- one of the ARCH_* constants
@@ -452,14 +452,11 @@ def update_host_via_sysinfo(opts)
452452
end
453453

454454
if info['OS'] =~ /^Windows\s*([^\(]+)\(([^\)]+)\)/i
455-
res[:os_name] = "Microsoft Windows"
456-
res[:os_flavor] = $1.strip
455+
res[:os_name] = "Windows #{$1.strip}"
457456
build = $2.strip
458457

459458
if build =~ /Service Pack (\d+)/
460459
res[:os_sp] = "SP" + $1
461-
else
462-
res[:os_sp] = "SP0"
463460
end
464461
end
465462

@@ -3531,7 +3528,18 @@ def import_spiceworks_csv(args={}, &block)
35313528
:task => args[:task]
35323529
}
35333530

3534-
conf[:os_name] = os if os
3531+
3532+
if os
3533+
report_note(
3534+
:workspace => wspace,
3535+
:task => args[:task],
3536+
:host => ip,
3537+
:type => 'host.os.spiceworks_fingerprint',
3538+
:data => {
3539+
:os => os.to_s.strip
3540+
}
3541+
)
3542+
end
35353543

35363544
info = []
35373545
info << "Serial Number: #{serialno}" unless (serialno.blank? or serialno == name)

lib/msf/core/exploit/http/client.rb

Lines changed: 115 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -439,53 +439,100 @@ def proxies
439439
datastore['Proxies']
440440
end
441441

442+
443+
#
444+
# Lookup HTTP fingerprints from the database that match the current
445+
# destination host and port. This method falls back to using the old
446+
# service.info field to represent the HTTP Server header.
447+
#
448+
# @option opts [String] :uri ('/') An HTTP URI to request in order to generate
449+
# a fingerprint
450+
# @option opts [String] :method ('GET') An HTTP method to use in the fingerprint
451+
# request
452+
def lookup_http_fingerprints(opts={})
453+
uri = opts[:uri] || '/'
454+
method = opts[:method] || 'GET'
455+
fprints = []
456+
457+
return fprints unless framework.db.active
458+
459+
::ActiveRecord::Base.connection_pool.with_connection {
460+
wspace = datastore['WORKSPACE'] ?
461+
framework.db.find_workspace(datastore['WORKSPACE']) : framework.db.workspace
462+
463+
service = framework.db.get_service(wspace, rhost, 'tcp', rport)
464+
return fprints unless service
465+
466+
# Order by note_id descending so the first value is the most recent
467+
service.notes.where(:ntype => 'http.fingerprint').order("notes.id DESC").each do |n|
468+
next unless n.data && n.data.kind_of?(::Hash)
469+
next unless n.data[:uri] == uri && n.data[:method] == method
470+
# Append additional fingerprints to the results as found
471+
fprints.unshift n.data.dup
472+
end
473+
}
474+
475+
fprints
476+
end
477+
442478
#
443479
# Record various things about an HTTP server that we can glean from the
444480
# response to a single request. If this method is passed a response, it
445481
# will use it directly, otherwise it will check the database for a previous
446482
# fingerprint. Failing that, it will make a request for /.
447483
#
448-
# Options:
449-
# :response an Http::Packet as returned from any of the send_* methods
484+
# Other options are passed directly to {#connect} if :response is not given
450485
#
451-
# Other options are passed directly to +connect+ if :response is not given
486+
# @option opts [Rex::Proto::Http::Packet] :response The return value from any
487+
# of the send_* methods
488+
# @option opts [String] :uri ('/') An HTTP URI to request in order to generate
489+
# a fingerprint
490+
# @option opts [String] :method ('GET') An HTTP method to use in the fingerprint
491+
# request
492+
# @option opts [Boolean] :full (false) Request the full HTTP fingerprint, not
493+
# just the signature
452494
#
495+
# @return [String]
453496
def http_fingerprint(opts={})
497+
res = nil
498+
uri = opts[:uri] || '/'
499+
method = opts[:method] || 'GET'
454500

455-
if (opts[:response])
501+
# Short-circuit the fingerprint lookup and HTTP request if a response has
502+
# already been provided by the caller.
503+
if opts[:response]
456504
res = opts[:response]
457505
else
458-
# Check to see if we already have a fingerprint before going out to
459-
# the network.
460-
if (framework.db.active)
461-
::ActiveRecord::Base.connection_pool.with_connection {
462-
wspace = framework.db.workspace
463-
if datastore['WORKSPACE']
464-
wspace = framework.db.find_workspace(datastore['WORKSPACE'])
465-
end
506+
fprints = lookup_http_fingerprints(opts)
466507

467-
s = framework.db.get_service(wspace, rhost, 'tcp', rport)
468-
if (s and s.info)
469-
return s.info
470-
end
471-
}
508+
if fprints.length > 0
509+
510+
# Grab the most recent fingerprint available for this service, uri, and method
511+
fprint = fprints.last
512+
513+
# Return the full HTTP fingerprint if requested by the caller
514+
return fprint if opts[:full]
515+
516+
# Otherwise just return the signature string for compatibility
517+
return fprint[:signature]
472518
end
473519

520+
# Go ahead and send a request to the target for fingerprinting
474521
connect(opts)
475-
uri = opts[:uri] || '/'
476-
method = opts[:method] || 'GET'
477522
res = send_request_raw(
478523
{
479524
'uri' => uri,
480525
'method' => method
481526
})
482527
end
483528

484-
# Bail if we don't have anything to fingerprint
529+
# Bail if the request did not receive a readable response
485530
return if not res
486531

487-
# From here to the end simply does some pre-canned combining and custom matches
488-
# to build a human-readable string to store in service.info
532+
# This section handles a few simple cases of pattern matching and service
533+
# classification. This logic should be deprecated in favor of Recog-based
534+
# fingerprint databases, but has been left in place for backward compat.
535+
489536
extras = []
490537

491538
if res.headers['Set-Cookie'] =~ /^vmware_soap_session/
@@ -537,6 +584,11 @@ def http_fingerprint(opts={})
537584
end
538585
end
539586

587+
#
588+
# This HTTP response code tracking is used by a few modules and the MSP logic
589+
# to identify and bruteforce certain types of servers. In the long run we
590+
# should deprecate this and use the http.fingerprint fields instead.
591+
#
540592
case res.code
541593
when 301,302
542594
extras << "#{res.code}-#{res.headers['Location']}"
@@ -548,12 +600,51 @@ def http_fingerprint(opts={})
548600
extras << "#{res.code}-#{res.message}"
549601
end
550602

551-
info = "#{res.headers['Server']}"
603+
# Build a human-readable string to store in service.info and http.fingerprint[:signature]
604+
info = res.headers['Server'].to_s.dup
552605
info << " ( #{extras.join(", ")} )" if extras.length > 0
606+
607+
# Create a new fingerprint structure to track this response
608+
fprint = {
609+
:uri => uri, :method => method,
610+
:code => res.code.to_s, :message => res.message.to_s,
611+
:signature => info
612+
}
613+
614+
res.headers.each_pair do |k,v|
615+
hname = k.to_s.downcase.gsub('-', '_').gsub(/[^a-z0-9_]+/, '')
616+
next unless hname.length > 0
617+
618+
# Set-Cookie > :header_set_cookie => JSESSIONID=AAASD23423452
619+
# Server > :header_server => Apache/1.3.37
620+
# WWW-Authenticate > :header_www_authenticate => basic realm='www'
621+
622+
fprint["header_#{hname}".intern] = v
623+
end
624+
625+
# Store the first 64k of the HTTP body as well
626+
fprint[:content] = res.body.to_s[0,65535]
627+
628+
# Report a new http.fingerprint note
629+
report_note(
630+
:host => rhost,
631+
:port => rport,
632+
:proto => 'tcp',
633+
:ntype => 'http.fingerprint',
634+
:data => fprint,
635+
# Limit reporting to one stored note per host/service combination
636+
:update => :unique
637+
)
638+
553639
# Report here even if info is empty since the fact that we didn't
554640
# return early means we at least got a connection and the service is up
555641
report_web_site(:host => rhost, :port => rport, :ssl => ssl, :vhost => vhost, :info => info.dup)
556-
info
642+
643+
# Return the full HTTP fingerprint if requested by the caller
644+
return fprint if opts[:full]
645+
646+
# Otherwise just return the signature string for compatibility
647+
fprint[:signature]
557648
end
558649

559650
def make_cnonce

0 commit comments

Comments
 (0)