metasploit_data_models 0.21.0-java → 0.21.2-java

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d35554409458b75e4cc8fbe2b49f658606391248
4
- data.tar.gz: 2cd9308c86a09a5018db91e5bdf25ae13733dde8
3
+ metadata.gz: f5d014244a6ceb23191cb6cfbb5a164d76443659
4
+ data.tar.gz: 7716d163221b3a3a50de7f4943c5091a6b0a7c18
5
5
  SHA512:
6
- metadata.gz: 11be59f76afc4eb8dfaf1de8cf1bbaff5d14f5f25f61d7f0dbc7e67d6d43c5b58993e4ede91bd1907bfef7a2642c3a00e19925d13712e17ec7718e059bad055b
7
- data.tar.gz: 0d2dc7cd4b38c97b211b3afcde3daf790f1a3b71c9e92a0e6c6ed37cd1cfb8ad938c5cc4a9836e4bd08f9bc403aa5997caa10b8cb413865011b29d55eb3455fe
6
+ metadata.gz: f7c311fd18000e55dcd1e8d7551cfee13b2ff119930fa2c9d37160dfc2bdfe2299160ff886a3e8190f099f52a278f6ed8a459d6b125b5f348d29e459dbf5b5fe
7
+ data.tar.gz: 46f5e6ae510c54b334121290c8b5d5d861ddf691e16e80713d803a6df934822831fcbecf0c13a4c643faa8b1e9555238fdd16ad07e701837378a70a11c677441
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source "http://rubygems.org"
1
+ source "https://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in metasploit_data_models.gemspec
4
4
  gemspec
@@ -10,6 +10,8 @@ end
10
10
 
11
11
  # used by dummy application
12
12
  group :development, :test do
13
+ # Upload coverage reports to coveralls.io
14
+ gem 'coveralls', require: false
13
15
  # supplies factories for producing model instance for specs
14
16
  # Version 4.1.0 or newer is needed to support generate calls without the 'FactoryGirl.' in factory definitions syntax.
15
17
  gem 'factory_girl', '>= 4.1.0'
@@ -24,8 +26,6 @@ group :development, :test do
24
26
  end
25
27
 
26
28
  group :test do
27
- # Upload coverage reports to coveralls.io
28
- gem 'coveralls', require: false
29
29
  # In a full rails project, factory_girl_rails would be in both the :development, and :test group, but since we only
30
30
  # want rails in :test, factory_girl_rails must also only be in :test.
31
31
  # add matchers from shoulda, such as validates_presence_of, which are useful for testing validations
@@ -359,7 +359,7 @@ class Mdm::Host < ActiveRecord::Base
359
359
  # The flavor of {#os_name}.
360
360
  #
361
361
  # @example Windows XP
362
- # host.os_name = 'Microsoft Windows'
362
+ # host.os_name = 'Windows'
363
363
  # host.os_flavor = 'XP'
364
364
  #
365
365
  # @return [String]
@@ -379,7 +379,7 @@ class Mdm::Host < ActiveRecord::Base
379
379
  # The service pack of the {#os_flavor} of the {#os_name}.
380
380
  #
381
381
  # @example Windows XP SP2
382
- # host.os_name = 'Microsoft Windows'
382
+ # host.os_name = 'Windows'
383
383
  # host.os_flavor = 'XP'
384
384
  # host.os_sp = 'SP2'
385
385
  #
@@ -1,705 +1,292 @@
1
+ #
2
+ # Leverage the Recog gem as much as possible for sane fingerprint management
3
+ #
4
+ require 'recog'
5
+
6
+ #
7
+ # Rules for operating system fingerprinting in Metasploit
8
+ #
9
+ # The `os.product` key identifies the common-name of a specific operating system
10
+ # Examples include: Linux, Windows XP, Mac OS X, IOS, AIX, HP-UX, VxWorks
11
+ #
12
+ # The `os.version` key identifies the service pack or version of the operating system
13
+ # Sometimes this means a kernel or firmware version when the distribution or OS
14
+ # version is not available.
15
+ # Examples include: SP2, 10.04, 2.6.47, 10.6.1
16
+ #
17
+ # The `os.vendor` key identifies the manufacturer of the operating system
18
+ # Examples include: Microsoft, Ubuntu, Cisco, HP, IBM, Wind River
19
+ #
20
+ # The `os.family` key identifies the group of the operating system. This is often a
21
+ # duplicate of os.product, unless a more specific product name is available.
22
+ # Examples include: Windows, Linux, IOS, HP-UX, AIX
23
+ #
24
+ # The `os.edition` key identifies the specific variant of the operating system
25
+ # Examples include: Enterprise, Professional, Starter, Evaluation, Home, Datacenter
26
+ #
27
+ # An example breakdown of a common operating system is shown below
28
+ #
29
+ # * Microsoft Windows XP Professional Service Pack 3 English (x86)
30
+ # - os.product = 'Windows XP'
31
+ # - os.edition = 'Professional'
32
+ # - os.vendor = 'Microsoft'
33
+ # - os.version = 'SP3'
34
+ # - os.language = 'English'
35
+ # - os.arch = 'x86'
36
+ #
37
+ # These rules are then mapped to the {Mdm::Host} attributes below:
38
+ #
39
+ # * os_name - Maps to a normalized os.product key
40
+ # * os_flavor - Maps to a normalized os.edition key
41
+ # * os_sp - Maps to a normalized os.version key (soon os_version)
42
+ # * os_lang - Maps to a normalized os.language key
43
+ # * arch - Maps to a normalized os.arch key
44
+ #
45
+ # Additional rules include the following mappings:
46
+ #
47
+ # * name - Maps to the host.name key
48
+ # * mac - Maps to the host.mac key
49
+ #
50
+ # The following keys are not mapped to {Mdm::Host} at this time (but should be):
51
+ #
52
+ # * os.vendor
53
+ #
54
+ # In order to execute these rules, this module is responsible for mapping various
55
+ # fingerprint sources to {Mdm::Host} values. This requires some ugly glue code to
56
+ # account for differences between each supported input (external scanners), the
57
+ # Recog gem and associated databases, and how Metasploit itself likes to handle
58
+ # these values. Getting a mapping wrong is often harmless, but can impact the
59
+ # automatic targetting capabilities of certain exploit modules.
60
+ #
61
+ # In other words, this is a best-effort attempt to rationalize multiple competing
62
+ # sources of information about a host and come up with the values representing a
63
+ # normalized assessment of the system. The use of `Recog` and multiple scanner
64
+ # fingerprints can result in a comprehensive (and confident) identification of the
65
+ # remote operating system and associated services.
66
+ #
67
+ # Historically, there are direct conflicts between certain Metasploit modules,
68
+ # certain scanners, and external fingerprint databases in terms of how a
69
+ # particular OS and patch level is represented. This module attempts to fix what
70
+ # it can and serve as documentation and live workarounds for the rest.
71
+ #
72
+ # Examples of known conflicts that are still in progress:
73
+ #
74
+ # * Metasploit defines an OS constant of 'win'/'windows' as Microsoft Windows
75
+ #
76
+ # - Scanner modules report a mix of 'Microsoft Windows' and 'Windows'
77
+ # - Nearly all exploit modules reference 'Windows <Release> SP<Version>'
78
+ # - Nmap (and other scanners) also prefix the vendor before Windows
79
+ #
80
+ #
81
+ # * Windows service packs represented as 'Service Pack X' or 'SPX'
82
+ #
83
+ # - The preferred form is to set os.version to 'SPX'
84
+ # - Many external scanners & Recog prefer 'Service Pack X'
85
+ #
86
+ # * Apple Mac OS X, Cisco IOS, IBM AIX, Ubuntu Linux, all reported with vendor prefix
87
+ #
88
+ # - The preferred form is to remove the vendor from os.product
89
+ # - {Mdm::Host} currently has no vendor field, so this information is lost today
90
+ # - Many scanners report leading vendor strings and require normalization
91
+ #
92
+ # * The os_flavor field is used in contradictory ways across Metasploit
93
+ #
94
+ # - The preferred form is to be a 'display only' field
95
+ # - Some Recog fingerprints still append the edition to os.product
96
+ # - Many scanners report the edition as a trailing suffix to os.product
97
+ #
98
+ #
99
+ #
100
+ #
101
+ # Maintenance:
102
+ #
103
+ # 1. Ensure that the latest Recog gem is present and installed
104
+ # 2. For new operating system releases, update relevant sections
105
+ # a) Windows releases will require updates to a few methods
106
+ # 1) parse_windows_os_str()
107
+ # 2) normalize_nmap_fingerprint()
108
+ # 3) normalize_nexpose_fingerprint()
109
+ # 4) Other scanner normalizers
110
+ # b) Mobile operating systems are minimally recognized
111
+ #
112
+ #
113
+ # @todo Handle OS icon incompatiblities with new fingerprint names
114
+ # Note that VMWare ESX(i) was special cased before as well, make sure it still works
115
+ # 1) Cisco IOS -> IOS breaks the icon mapping in MSP/MSCE of /cisco/
116
+ # 2) Ubuntu Linux -> Linux breaks the distro selection
117
+ # The real solution is to add os_vendor and take this into account for icons
118
+ #
119
+ # @todo Implement rspec coverage for normalize_os()
120
+ # @todo Implement smb.generic fingerprint database (replace {#parse_windows_os_str}?)
121
+ # @todo Implement Samba version matching for specific distributions and OS versions
122
+ # @todo Implement DD-WRT and various embedded device signatures currently missing
123
+ # @todo Correct inconsistencies in os_name use by removing the vendor string (Microsoft Windows -> Windows)
124
+ # This applies to MSF core and a handful of modules, not to mention some Recog fingerprints.
125
+ # @todo Rename host.os_sp to host.os_version
126
+ # @todo Add host.os_vendor
127
+ # @todo Add host.os_confidence
128
+ # @todo Add host.domain
129
+ #
1
130
  module Mdm::Host::OperatingSystemNormalization
131
+
132
+ # Cap nmap certainty at 0.84 until we update it more frequently
133
+ # XXX: Without this, Nmap will beat the default certainty of recog
134
+ # matches and its less-confident guesses will take precedence
135
+ # over service-based fingerprints.
136
+ MAX_NMAP_CERTAINTY = 0.84
137
+
2
138
  #
3
139
  # Normalize the operating system fingerprints provided by various scanners
4
- # (nmap, nexpose, retina, nessus, etc).
140
+ # (nmap, nexpose, retina, nessus, metasploit modules, and more!)
5
141
  #
6
- # These are stored as notes (instead of directly in the os_* fields)
7
- # specifically for this purpose.
142
+ # These are stored as {Mdm::Note notes} (instead of directly in the os_*
143
+ # fields) specifically for this purpose.
144
+ #
145
+ # The goal is to infer as much as we can about the OS of the device and the
146
+ # various {Mdm::Service services} offered using the Recog gem and some glue
147
+ # logic to determine the best weights. This method can result in changes to
148
+ # the recorded {#os_name}, {#os_flavor}, {#os_sp}, {#os_lang}, {#purpose},
149
+ # {#name}, {#arch}, and the {Mdm::Service service details}.
8
150
  #
9
151
  def normalize_os
10
- host = self
11
-
12
- wname = {} # os_name == Linux, Windows, Mac OS X, VxWorks
13
- wtype = {} # purpose == server, client, device
14
- wflav = {} # os_flavor == Ubuntu, Debian, 2003, 10.5, JetDirect
15
- wvers = {} # os_sp == 9.10, SP2, 10.5.3, 3.05
16
- warch = {} # arch == x86, PPC, SPARC, MIPS, ''
17
- wlang = {} # os_lang == English, ''
18
- whost = {} # hostname
152
+ host = self
153
+ matches = []
19
154
 
20
155
  # Note that we're already restricting the query to this host by using
21
156
  # host.notes instead of Note, so don't need a host_id in the
22
157
  # conditions.
23
158
  fingerprintable_notes = self.notes.where("ntype like '%%fingerprint'")
24
- fingerprintable_notes.each do |fp|
25
- next if not validate_fingerprint_data(fp)
26
- norm = normalize_scanner_fp(fp)
27
- wvers[norm[:os_sp]] = wvers[norm[:os_sp]].to_i + (100 * norm[:certainty])
28
- wname[norm[:os_name]] = wname[norm[:os_name]].to_i + (100 * norm[:certainty])
29
- wflav[norm[:os_flavor]] = wflav[norm[:os_flavor]].to_i + (100 * norm[:certainty])
30
- warch[norm[:arch]] = warch[norm[:arch]].to_i + (100 * norm[:certainty])
31
- whost[norm[:name]] = whost[norm[:name]].to_i + (100 * norm[:certainty])
32
- wtype[norm[:type]] = wtype[norm[:type]].to_i + (100 * norm[:certainty])
33
- end
34
-
35
- # Grab service information and assign scores. Some services are
36
- # more trustworthy than others. If more services agree than not,
37
- # than that should be considered as well.
38
- # Each service has a starting number of points. Services that
39
- # are more difficult to fake are awarded more points. The points
40
- # represent a running total, not a fixed score.
41
- # XXX: This needs to be refactored in a big way. Tie-breaking is
42
- # pretty arbitrary, it would be nice to explicitly believe some
43
- # services over others, but that means recording which service
44
- # has an opinion and which doesn't. It would also be nice to
45
- # identify "impossible" combinations of services and alert that
46
- # something funny is going on.
159
+ fingerprintable_notes.each do |fp_note|
160
+ matches += recog_matches_for_note(fp_note)
161
+ end
162
+
47
163
  # XXX: This hack solves the memory leak generated by self.services.each {}
48
164
  fingerprintable_services = self.services.where("name is not null and name != '' and info is not null and info != ''")
49
165
  fingerprintable_services.each do |s|
50
- points = 0
51
- case s.name
52
- when 'smb'
53
- points = 210
54
- case s.info
55
- when /\.el([23456])(\s+|$)/ # Match Samba 3.0.33-0.30.el4 as RHEL4
56
- wname['Linux'] = wname['Linux'].to_i + points
57
- wflav["RHEL" + $1] = wflav["RHEL" + $1].to_i + points
58
- wtype['server'] = wtype['server'].to_i + points
59
- when /(ubuntu|debian|fedora|red ?hat|rhel)/i
60
- wname['Linux'] = wname['Linux'].to_i + points
61
- wflav[$1.capitalize] = wflav[$1.capitalize].to_i + points
62
- wtype['server'] = wtype['server'].to_i + points
63
- when /^Windows/
64
- win_sp = nil
65
- win_flav = nil
66
- win_lang = nil
67
-
68
- ninfo = s.info
69
- ninfo.gsub!('(R)', '')
70
- ninfo.gsub!('(TM)', '')
71
- ninfo.gsub!(/\s+/, ' ')
72
- ninfo.gsub!('No Service Pack', 'Service Pack 0')
73
-
74
- # Windows (R) Web Server 2008 6001 Service Pack 1 (language: Unknown) (name:PG-WIN2008WEB) (domain:WORKGROUP)
75
- # Windows XP Service Pack 3 (language: English) (name:EGYPT-B3E55BF3C) (domain:EGYPT-B3E55BF3C)
76
- # Windows 7 Ultimate (Build 7600) (language: Unknown) (name:WIN7) (domain:WORKGROUP)
77
- # Windows 2003 No Service Pack (language: Unknown) (name:VMWIN2003) (domain:PWNME)
78
-
79
- #if ninfo =~ /^Windows ([^\s]+)(.*)(Service Pack |\(Build )([^\(]+)\(/
80
- if ninfo =~ /^Windows (.*)(Service Pack [^\s]+|\(Build [^\)]+\))/
81
- win_flav = $1.strip
82
- win_sp = ($2).strip
83
- win_sp.gsub!(/with.*/, '')
84
- win_sp.gsub!('Service Pack', 'SP')
85
- win_sp.gsub!('Build', 'b')
86
- win_sp.gsub!(/\s+/, '')
87
- win_sp.tr!("()", '')
88
- else
89
- if ninfo =~ /^Windows ([^\s+]+)([^\(]+)\(/
90
- win_flav = $2.strip
91
- end
92
- end
93
-
94
-
95
- if ninfo =~ /name: ([^\)]+)\)/
96
- hostname = $1.strip
97
- end
98
-
99
- if ninfo =~ /language: ([^\)]+)\)/
100
- win_lang = $1.strip
101
- end
102
-
103
- win_lang = nil if win_lang =~ /unknown/i
104
- win_vers = win_sp
105
-
106
- wname['Microsoft Windows'] = wname['Microsoft Windows'].to_i + points
107
- wlang[win_lang] = wlang[win_lang].to_i + points if win_lang
108
- wflav[win_flav] = wflav[win_flav].to_i + points if win_flav
109
- wvers[win_vers] = wvers[win_vers].to_i + points if win_vers
110
- whost[hostname] = whost[hostname].to_i + points if hostname
111
-
112
- case win_flav
113
- when /NT|2003|2008/
114
- win_type = 'server'
115
- else
116
- win_type = 'client'
117
- end
118
- wtype[win_type] = wtype[win_type].to_i + points
119
- end
166
+ matches += recog_matches_for_service(s)
167
+ end
120
168
 
121
- when 'ssh'
122
- points = 104
123
- case s.info
124
- when /honeypot/i # Never trust this
125
- nil
126
- when /ubuntu/i
127
- # This needs to be above /debian/ becuase the ubuntu banner contains both, e.g.:
128
- # SSH-2.0-OpenSSH_5.3p1 Debian-3ubuntu6
129
- wname['Linux'] = wname['Linux'].to_i + points
130
- wflav['Ubuntu'] = wflav['Ubuntu'].to_i + points
131
- wtype['server'] = wtype['server'].to_i + points
132
- when /debian/i
133
- wname['Linux'] = wname['Linux'].to_i + points
134
- wflav['Debian'] = wflav['Debian'].to_i + points
135
- wtype['server'] = wtype['server'].to_i + points
136
- when /FreeBSD/
137
- wname['FreeBSD'] = wname['FreeBSD'].to_i + points
138
- wtype['server'] = wtype['server'].to_i + points
139
- when /sun_ssh/i
140
- wname['Sun Solaris'] = wname['Sun Solaris'].to_i + points
141
- wtype['server'] = wtype['server'].to_i + points
142
- when /vshell|remotelyanywhere|freessh/i
143
- wname['Microsoft Windows'] = wname['Microsoft Windows'].to_i + points
144
- wtype['server'] = wtype['server'].to_i + points
145
-
146
- when /radware/i
147
- wname['RadWare'] = wname['RadWare'].to_i + points
148
- wtype['device'] = wtype['device'].to_i + points
149
-
150
- when /dropbear/i
151
- wname['Linux'] = wname['Linux'].to_i + points
152
- wtype['device'] = wtype['device'].to_i + points
153
-
154
- when /netscreen/i
155
- wname['NetScreen'] = wname['NetScreen'].to_i + points
156
- wtype['device'] = wtype['device'].to_i + points
157
-
158
- when /vpn3/
159
- wname['Cisco VPN 3000'] = wname['Cisco VPN 3000'].to_i + points
160
- wtype['device'] = wtype['device'].to_i + points
161
-
162
- when /cisco/i
163
- wname['Cisco IOS'] = wname['Cisco IOS'].to_i + points
164
- wtype['device'] = wtype['device'].to_i + points
165
-
166
- when /mpSSH/
167
- wname['HP iLO'] = wname['HP iLO'].to_i + points
168
- wtype['server'] = wtype['server'].to_i + points
169
- end
170
- when 'http'
171
- points = 99
172
- case s.info
173
- when /iSeries/
174
- wname['IBM iSeries'] = wname['IBM iSeries'].to_i + points
175
- wtype['server'] = wtype['server'].to_i + points
176
-
177
- when /Mandrake/i
178
- wname['Linux'] = wname['Linux'].to_i + points
179
- wflav['Mandrake'] = wflav['Mandrake'].to_i + points
180
- wtype['server'] = wtype['server'].to_i + points
181
-
182
- when /Mandriva/i
183
- wname['Linux'] = wname['Linux'].to_i + points
184
- wflav['Mandrake'] = wflav['Mandrake'].to_i + points
185
- wtype['server'] = wtype['server'].to_i + points
186
-
187
- when /Ubuntu/i
188
- wname['Linux'] = wname['Linux'].to_i + points
189
- wflav['Ubuntu'] = wflav['Ubuntu'].to_i + points
190
- wtype['server'] = wtype['server'].to_i + points
191
-
192
- when /Debian/i
193
- wname['Linux'] = wname['Linux'].to_i + points
194
- wflav['Debian'] = wflav['Debian'].to_i + points
195
- wtype['server'] = wtype['server'].to_i + points
196
-
197
- when /Fedora/i
198
- wname['Linux'] = wname['Linux'].to_i + points
199
- wflav['Fedora'] = wflav['Fedora'].to_i + points
200
- wtype['server'] = wtype['server'].to_i + points
201
-
202
- when /CentOS/i
203
- wname['Linux'] = wname['Linux'].to_i + points
204
- wflav['CentOS'] = wflav['CentOS'].to_i + points
205
- wtype['server'] = wtype['server'].to_i + points
206
-
207
- when /RHEL/i
208
- wname['Linux'] = wname['Linux'].to_i + points
209
- wflav['RHEL'] = wflav['RHEL'].to_i + points
210
- wtype['server'] = wtype['server'].to_i + points
211
-
212
- when /Red.?Hat/i
213
- wname['Linux'] = wname['Linux'].to_i + points
214
- wflav['Red Hat'] = wflav['Red Hat'].to_i + points
215
- wtype['server'] = wtype['server'].to_i + points
216
-
217
- when /SuSE/i
218
- wname['Linux'] = wname['Linux'].to_i + points
219
- wflav['SUSE'] = wflav['SUSE'].to_i + points
220
- wtype['server'] = wtype['server'].to_i + points
221
-
222
- when /TurboLinux/i
223
- wname['Linux'] = wname['Linux'].to_i + points
224
- wflav['TurboLinux'] = wflav['TurboLinux'].to_i + points
225
- wtype['server'] = wtype['server'].to_i + points
226
-
227
- when /Gentoo/i
228
- wname['Linux'] = wname['Linux'].to_i + points
229
- wflav['Gentoo'] = wflav['Gentoo'].to_i + points
230
- wtype['server'] = wtype['server'].to_i + points
231
-
232
- when /Conectiva/i
233
- wname['Linux'] = wname['Linux'].to_i + points
234
- wflav['Conectiva'] = wflav['Conectiva'].to_i + points
235
- wtype['server'] = wtype['server'].to_i + points
236
-
237
- when /Asianux/i
238
- wname['Linux'] = wname['Linux'].to_i + points
239
- wflav['Asianux'] = wflav['Asianux'].to_i + points
240
- wtype['server'] = wtype['server'].to_i + points
241
-
242
- when /Trustix/i
243
- wname['Linux'] = wname['Linux'].to_i + points
244
- wflav['Trustix'] = wflav['Trustix'].to_i + points
245
- wtype['server'] = wtype['server'].to_i + points
246
-
247
- when /White Box/
248
- wname['Linux'] = wname['Linux'].to_i + points
249
- wflav['White Box'] = wflav['White Box'].to_i + points
250
- wtype['server'] = wtype['server'].to_i + points
251
-
252
- when /UnitedLinux/
253
- wname['Linux'] = wname['Linux'].to_i + points
254
- wflav['UnitedLinux'] = wflav['UnitedLinux'].to_i + points
255
- wtype['server'] = wtype['server'].to_i + points
256
-
257
- when /PLD\/Linux/
258
- wname['Linux'] = wname['Linux'].to_i + points
259
- wflav['PLD/Linux'] = wflav['PLD/Linux'].to_i + points
260
- wtype['server'] = wtype['server'].to_i + points
261
-
262
- when /Vine\/Linux/
263
- wname['Linux'] = wname['Linux'].to_i + points
264
- wflav['Vine/Linux'] = wflav['Vine/Linux'].to_i + points
265
- wtype['server'] = wtype['server'].to_i + points
266
-
267
- when /rPath/
268
- wname['Linux'] = wname['Linux'].to_i + points
269
- wflav['rPath'] = wflav['rPath'].to_i + points
270
- wtype['server'] = wtype['server'].to_i + points
271
-
272
- when /StartCom/
273
- wname['Linux'] = wname['Linux'].to_i + points
274
- wflav['StartCom'] = wflav['StartCom'].to_i + points
275
- wtype['server'] = wtype['server'].to_i + points
276
-
277
- when /linux/i
278
- wname['Linux'] = wname['Linux'].to_i + points
279
- wtype['server'] = wtype['server'].to_i + points
280
-
281
- when /PalmOS/
282
- wname['PalmOS'] = wname['PalmOS'].to_i + points
283
- wtype['device'] = wtype['device'].to_i + points
284
-
285
- when /Microsoft[\x20\x2d]IIS\/[234]\.0/
286
- wname['Microsoft Windows NT 4.0'] = wname['Microsoft Windows NT 4.0'].to_i + points
287
- wtype['server'] = wtype['server'].to_i + points
288
-
289
- when /Microsoft[\x20\x2d]IIS\/5\.0/
290
- wname['Microsoft Windows 2000'] = wname['Microsoft Windows 2000'].to_i + points
291
- wtype['server'] = wtype['server'].to_i + points
292
-
293
- when /Microsoft[\x20\x2d]IIS\/5\.1/
294
- wname['Microsoft Windows XP'] = wname['Microsoft Windows XP'].to_i + points
295
- wtype['server'] = wtype['server'].to_i + points
296
-
297
- when /Microsoft[\x20\x2d]IIS\/6\.0/
298
- wname['Microsoft Windows 2003'] = wname['Microsoft Windows 2003'].to_i + points
299
- wtype['server'] = wtype['server'].to_i + points
300
-
301
- when /Microsoft[\x20\x2d]IIS\/7\.0/
302
- wname['Microsoft Windows 2008'] = wname['Microsoft Windows 2008'].to_i + points
303
- wtype['server'] = wtype['server'].to_i + points
304
-
305
- when /Win32/i
306
- wname['Microsoft Windows'] = wname['Microsoft Windows'].to_i + points
307
- wtype['server'] = wtype['server'].to_i + points
308
-
309
- when /DD\-WRT ([^\s]+) /i
310
- wname['Linux'] = wname['Linux'].to_i + points
311
- wflav['DD-WRT'] = wflav['DD-WRT'].to_i + points
312
- wvers[$1.strip] = wvers[$1.strip].to_i + points
313
- wtype['server'] = wtype['server'].to_i + points
314
-
315
- when /Darwin/
316
- wname['Apple Mac OS X'] = wname['Apple Mac OS X'].to_i + points
317
-
318
- when /FreeBSD/i
319
- wname['FreeBSD'] = wname['FreeBSD'].to_i + points
320
-
321
- when /OpenBSD/i
322
- wname['OpenBSD'] = wname['OpenBSD'].to_i + points
323
-
324
- when /NetBSD/i
325
- wname['NetBSD'] = wname['NetBSD'].to_i + points
326
-
327
- when /NetWare/i
328
- wname['Novell NetWare'] = wname['Novell NetWare'].to_i + points
329
-
330
- when /OpenVMS/i
331
- wname['OpenVMS'] = wname['OpenVMS'].to_i + points
332
-
333
- when /SunOS|Solaris/i
334
- wname['Sun Solaris'] = wname['Sun Solaris'].to_i + points
169
+ #
170
+ # Look for generic fingerprint.match notes that generate a match hash from modules
171
+ # This handles ad-hoc os.language, host.name, etc identifications
172
+ #
173
+ generated_matches = self.notes.where(ntype: 'fingerprint.match')
174
+ generated_matches.each do |m|
175
+ next unless (m.data and m.data.kind_of?(::Hash))
176
+ matches << m.data.dup
177
+ end
335
178
 
336
- when /HP.?UX/i
337
- wname['HP-UX'] = wname['HP-UX'].to_i + points
338
- end
339
- when 'snmp'
340
- points = 103
341
- case s.info
342
- when /^Sun SNMP Agent/
343
- wname['Sun Solaris'] = wname['Sun Solaris'].to_i + points
344
- wtype['server'] = wtype['server'].to_i + points
345
-
346
- when /^SunOS ([^\s]+) ([^\s]+) /
347
- # XXX 1/2 XXX what does this comment mean i wonder
348
- wname['Sun Solaris'] = wname['Sun Solaris'].to_i + points
349
- wtype['server'] = wtype['server'].to_i + points
350
-
351
- when /^Linux ([^\s]+) ([^\s]+) /
352
- whost[$1] = whost[$1].to_i + points
353
- wname['Linux ' + $2] = wname['Linux ' + $2].to_i + points
354
- wvers[$2] = wvers[$2].to_i + points
355
- arch = get_arch_from_string(s.info)
356
- warch[arch] = warch[arch].to_i + points if arch
357
- wtype['server'] = wtype['server'].to_i + points
358
-
359
- when /^Novell NetWare ([^\s]+)/
360
- wname['Novell NetWare ' + $1] = wname['Novell NetWare ' + $1].to_i + points
361
- wvers[$1] = wvers[$1].to_i + points
362
- arch = "x86"
363
- warch[arch] = warch[arch].to_i + points
364
- wtype['server'] = wtype['server'].to_i + points
365
-
366
- when /^Novell UnixWare ([^\s]+)/
367
- wname['Novell UnixWare ' + $1] = wname['Novell UnixWare ' + $1].to_i + points
368
- wvers[$1] = wvers[$1].to_i + points
369
- arch = "x86"
370
- warch[arch] = warch[arch].to_i + points
371
- wtype['server'] = wtype['server'].to_i + points
372
-
373
- when /^HP-UX ([^\s]+) ([^\s]+) /
374
- # XXX
375
- wname['HP-UX ' + $2] = wname['HP-UX ' + $2].to_i + points
376
- wvers[$1] = wvers[$1].to_i + points
377
- wtype['server'] = wtype['server'].to_i + points
378
-
379
- when /^IBM PowerPC.*Base Operating System Runtime AIX version: (\d+\.\d+)/
380
- wname['IBM AIX ' + $1] = wname['IBM AIX ' + $1].to_i + points
381
- wvers[$1] = wvers[$1].to_i + points
382
- wtype['server'] = wtype['server'].to_i + points
383
-
384
- when /^SCO TCP\/IP Runtime Release ([^\s]+)/
385
- wname['SCO UnixWare ' + $1] = wname['SCO UnixWare ' + $1].to_i + points
386
- wvers[$1] = wvers[$1].to_i + points
387
- wtype['server'] = wtype['server'].to_i + points
388
-
389
- when /.* IRIX version ([^\s]+)/
390
- wname['SGI IRIX ' + $1] = wname['SGI IRIX ' + $1].to_i + points
391
- wvers[$1] = wvers[$1].to_i + points
392
- wtype['server'] = wtype['server'].to_i + points
393
-
394
- when /^Unisys ([^\s]+) version ([^\s]+) kernel/
395
- wname['Unisys ' + $2] = wname['Unisys ' + $2].to_i + points
396
- wvers[$2] = wvers[$2].to_i + points
397
- whost[$1] = whost[$1].to_i + points
398
- wtype['server'] = wtype['server'].to_i + points
399
-
400
- when /.*OpenVMS V([^\s]+) /
401
- # XXX
402
- wname['OpenVMS ' + $1] = wname['OpenVMS ' + $1].to_i + points
403
- wvers[$1] = wvers[$1].to_i + points
404
- wtype['server'] = wtype['server'].to_i + points
405
-
406
- when /^Hardware:.*Software: Windows NT Version ([^\s]+) /
407
- wname['Microsoft Windows NT ' + $1] = wname['Microsoft Windows NT ' + $1].to_i + points
408
- wtype['server'] = wtype['server'].to_i + points
409
-
410
- when /^Hardware:.*Software: Windows 2000 Version 5\.0/
411
- wname['Microsoft Windows 2000'] = wname['Microsoft Windows 2000'].to_i + points
412
- wtype['server'] = wtype['server'].to_i + points
413
-
414
- when /^Hardware:.*Software: Windows 2000 Version 5\.1/
415
- wname['Microsoft Windows XP'] = wname['Microsoft Windows XP'].to_i + points
416
- wtype['server'] = wtype['server'].to_i + points
417
-
418
- when /^Hardware:.*Software: Windows Version 5\.2/
419
- wname['Microsoft Windows 2003'] = wname['Microsoft Windows 2003'].to_i + points
420
- wtype['server'] = wtype['server'].to_i + points
421
-
422
- # XXX: TODO 2008, Vista, Windows 7
423
-
424
- when /^Microsoft Windows CE Version ([^\s]+)+/
425
- wname['Microsoft Windows CE ' + $1] = wname['Microsoft Windows CE ' + $1].to_i + points
426
- wtype['client'] = wtype['client'].to_i + points
427
-
428
- when /^IPSO ([^\s]+) ([^\s]+) /
429
- whost[$1] = whost[$1].to_i + points
430
- wname['Nokia IPSO ' + $2] = wname['Nokia IPSO ' + $2].to_i + points
431
- wvers[$2] = wvers[$2].to_i + points
432
- arch = get_arch_from_string(s.info)
433
- warch[arch] = warch[arch].to_s + points if arch
434
- wtype['device'] = wtype['device'].to_i + points
435
-
436
- when /^Sun StorEdge/
437
- wname['Sun StorEdge'] = wname['Sun StorEdge'].to_i + points
438
- wtype['device'] = wtype['device'].to_i + points
439
-
440
- when /^HP StorageWorks/
441
- wname['HP StorageWorks'] = wname['HP StorageWorks'].to_i + points
442
- wtype['device'] = wtype['device'].to_i + points
443
-
444
- when /^Network Storage/
445
- # XXX
446
- wname['Network Storage Router'] = wname['Network Storage Router'].to_i + points
447
- wtype['device'] = wtype['device'].to_i + points
448
-
449
- when /Cisco Internetwork Operating System.*Version ([^\s]+)/
450
- vers = $1.split(/[,^\s]/)[0]
451
- wname['Cisco IOS ' + vers] = wname['Cisco IOS ' + vers].to_i + points
452
- wvers[vers] = wvers[vers].to_i + points
453
- wtype['device'] = wtype['device'].to_i + points
454
-
455
- when /Cisco Catalyst.*Version ([^\s]+)/
456
- vers = $1.split(/[,^\s]/)[0]
457
- wname['Cisco CatOS ' + vers] = wname['Cisco CatOS ' + vers].to_i + points
458
- wvers[vers] = wvers[vers].to_i + points
459
- wtype['device'] = wtype['device'].to_i + points
460
-
461
- when /Cisco 761.*Version ([^\s]+)/
462
- vers = $1.split(/[,^\s]/)[0]
463
- wname['Cisco 761 ' + vers] = wname['Cisco 761 ' + vers].to_i + points
464
- wvers[vers] = wvers[vers].to_i + points
465
- wtype['device'] = wtype['device'].to_i + points
466
-
467
- when /Network Analysis Module.*Version ([^\s]+)/
468
- vers = $1.split(/[,^\s]/)[0]
469
- wname['Cisco NAM ' + vers] = wname['Cisco NAM ' + vers].to_i + points
470
- wvers[vers] = wvers[vers].to_i + points
471
- wtype['device'] = wtype['device'].to_i + points
472
-
473
- when /VPN 3000 Concentrator Series Version ([^\s]+)/
474
- vers = $1.split(/[,^\s]/)[0]
475
- wname['Cisco VPN 3000 ' + vers] = wname['Cisco VPN 3000 ' + vers].to_i + points
476
- wvers[vers] = wvers[vers].to_i + points
477
- wtype['device'] = wtype['device'].to_i + points
478
-
479
- when /ProCurve.*Switch/
480
- wname['3Com ProCurve Switch'] = wname['3Com ProCurve Switch'].to_i + points
481
- wtype['device'] = wtype['device'].to_i + points
482
-
483
- when /ProCurve.*Access Point/
484
- wname['3Com Access Point'] = wname['3Com Access Point'].to_i + points
485
- wtype['device'] = wtype['device'].to_i + points
486
-
487
- when /3Com.*Access Point/i
488
- wname['3Com Access Point'] = wname['3Com Access Point'].to_i + points
489
- wtype['device'] = wtype['device'].to_i + points
490
-
491
- when /ShoreGear/
492
- wname['ShoreTel Appliance'] = wname['ShoreTel Appliance'].to_i + points
493
- wtype['device'] = wtype['device'].to_i + points
494
-
495
- when /firewall/i
496
- wname['Unknown Firewall'] = wname['Unknown Firewall'].to_i + points
497
- wtype['device'] = wtype['device'].to_i + points
498
-
499
- when /phone/i
500
- wname['Unknown Phone'] = wname['Unknown Phone'].to_i + points
501
- wtype['device'] = wtype['device'].to_i + points
502
-
503
- when /router/i
504
- wname['Unknown Router'] = wname['Unknown Router'].to_i + points
505
- wtype['device'] = wtype['device'].to_i + points
506
-
507
- when /switch/i
508
- wname['Unknown Switch'] = wname['Unknown Switch'].to_i + points
509
- wtype['device'] = wtype['device'].to_i + points
510
- #
511
- # Printer Signatures
512
- #
513
- when /^HP ETHERNET MULTI-ENVIRONMENT/
514
- wname['HP Printer'] = wname['HP Printer'].to_i + points
515
- wtype['printer'] = wtype['printer'].to_i + points
516
- when /Canon/i
517
- wname['Canon Printer'] = wname['Canon Printer'].to_i + points
518
- wtype['printer'] = wtype['printer'].to_i + points
519
- when /Epson/i
520
- wname['Epson Printer'] = wname['Epson Printer'].to_i + points
521
- wtype['printer'] = wtype['printer'].to_i + points
522
- when /ExtendNet/i
523
- wname['ExtendNet Printer'] = wname['ExtendNet Printer'].to_i + points
524
- wtype['printer'] = wtype['printer'].to_i + points
525
- when /Fiery/i
526
- wname['Fiery Printer'] = wname['Fiery Printer'].to_i + points
527
- wtype['printer'] = wtype['printer'].to_i + points
528
- when /Konica/i
529
- wname['Konica Printer'] = wname['Konica Printer'].to_i + points
530
- wtype['printer'] = wtype['printer'].to_i + points
531
- when /Lanier/i
532
- wname['Lanier Printer'] = wname['Lanier Printer'].to_i + points
533
- wtype['printer'] = wtype['printer'].to_i + points
534
- when /Lantronix/i
535
- wname['Lantronix Printer'] = wname['Lantronix Printer'].to_i + points
536
- wtype['printer'] = wtype['printer'].to_i + points
537
- when /Lexmark/i
538
- wname['Lexmark Printer'] = wname['Lexmark Printer'].to_i + points
539
- wtype['printer'] = wtype['printer'].to_i + points
540
- when /Magicolor/i
541
- wname['Magicolor Printer'] = wname['Magicolor Printer'].to_i + points
542
- wtype['printer'] = wtype['printer'].to_i + points
543
- when /Minolta/i
544
- wname['Minolta Printer'] = wname['Minolta Printer'].to_i + points
545
- wtype['printer'] = wtype['printer'].to_i + points
546
- when /NetJET/i
547
- wname['NetJET Printer'] = wname['NetJET Printer'].to_i + points
548
- wtype['printer'] = wtype['printer'].to_i + points
549
- when /OKILAN/i
550
- wname['OKILAN Printer'] = wname['OKILAN Printer'].to_i + points
551
- wtype['printer'] = wtype['printer'].to_i + points
552
- when /Phaser/i
553
- wname['Phaser Printer'] = wname['Phaser Printer'].to_i + points
554
- wtype['printer'] = wtype['printer'].to_i + points
555
- when /PocketPro/i
556
- wname['PocketPro Printer'] = wname['PocketPro Printer'].to_i + points
557
- wtype['printer'] = wtype['printer'].to_i + points
558
- when /Ricoh/i
559
- wname['Ricoh Printer'] = wname['Ricoh Printer'].to_i + points
560
- wtype['printer'] = wtype['printer'].to_i + points
561
- when /Savin/i
562
- wname['Savin Printer'] = wname['Savin Printer'].to_i + points
563
- wtype['printer'] = wtype['printer'].to_i + points
564
- when /SHARP AR/i
565
- wname['SHARP Printer'] = wname['SHARP Printer'].to_i + points
566
- wtype['printer'] = wtype['printer'].to_i + points
567
- when /Star Micronix/i
568
- wname['Star Micronix Printer'] = wname['Star Micronix Printer'].to_i + points
569
- wtype['printer'] = wtype['printer'].to_i + points
570
- when /Source Tech/i
571
- wname['Source Tech Printer'] = wname['Source Tech Printer'].to_i + points
572
- wtype['printer'] = wtype['printer'].to_i + points
573
- when /Xerox/i
574
- wname['Xerox Printer'] = wname['Xerox Printer'].to_i + points
575
- wtype['printer'] = wtype['printer'].to_i + points
576
- when /^Brother/i
577
- wname['Brother Printer'] = wname['Brother Printer'].to_i + points
578
- wtype['printer'] = wtype['printer'].to_i + points
579
- when /^Axis.*Network Print/i
580
- wname['Axis Printer'] = wname['Axis Printer'].to_i + points
581
- wtype['printer'] = wtype['printer'].to_i + points
582
- when /^Prestige/i
583
- wname['Prestige Printer'] = wname['Prestige Printer'].to_i + points
584
- wtype['printer'] = wtype['printer'].to_i + points
585
- when /^ZebraNet/i
586
- wname['ZebraNet Printer'] = wname['ZebraNet Printer'].to_i + points
587
- wtype['printer'] = wtype['printer'].to_i + points
588
- when /e\-STUDIO/i
589
- wname['eStudio Printer'] = wname['eStudio Printer'].to_i + points
590
- wtype['printer'] = wtype['printer'].to_i + points
591
- when /^Gestetner/i
592
- wname['Gestetner Printer'] = wname['Gestetner Printer'].to_i + points
593
- wtype['printer'] = wtype['printer'].to_i + points
594
- when /IBM.*Print/i
595
- wname['IBM Printer'] = wname['IBM Printer'].to_i + points
596
- wtype['printer'] = wtype['printer'].to_i + points
597
- when /HP (Color|LaserJet|InkJet)/i
598
- wname['HP Printer'] = wname['HP Printer'].to_i + points
599
- wtype['printer'] = wtype['printer'].to_i + points
600
- when /Dell (Color|Laser|Ink)/i
601
- wname['Dell Printer'] = wname['Dell Printer'].to_i + points
602
- wtype['printer'] = wtype['printer'].to_i + points
603
- when /Print/i
604
- wname['Unknown Printer'] = wname['Unknown Printer'].to_i + points
605
- wtype['printer'] = wtype['printer'].to_i + points
606
- end # End of s.info for SNMP
607
-
608
- when 'telnet'
609
- points = 105
610
- case s.info
611
- when /IRIX/
612
- wname['SGI IRIX'] = wname['SGI IRIX'].to_i + points
613
- when /AIX/
614
- wname['IBM AIX'] = wname['IBM AIX'].to_i + points
615
- when /(FreeBSD|OpenBSD|NetBSD)\/(.*) /
616
- wname[$1] = wname[$1].to_i + points
617
- arch = get_arch_from_string($2)
618
- warch[arch] = warch[arch].to_i + points
619
- when /Ubuntu (\d+(\.\d+)+)/
620
- wname['Linux'] = wname['Linux'].to_i + points
621
- wflav['Ubuntu'] = wflav['Ubuntu'].to_i + points
622
- wvers[$1] = wvers[$1].to_i + points
623
- when /User Access Verification/
624
- wname['Cisco IOS'] = wname['Cisco IOS'].to_i + points
625
- when /Microsoft/
626
- wname['Microsoft Windows'] = wname['Microsoft Windows'].to_i + points
627
- end # End of s.info for TELNET
628
- wtype['server'] = wtype['server'].to_i + points
629
-
630
- when 'smtp'
631
- points = 103
632
- case s.info
633
- when /ESMTP.*SGI\.8/
634
- wname['SGI IRIX'] = wname['SGI IRIX'].to_i + points
635
- wtype['server'] = wtype['server'].to_i + points
636
- end # End of s.info for SMTP
637
-
638
- when 'https'
639
- points = 101
640
- case s.info
641
- when /(VMware\s(ESXi?)).*\s([\d\.]+)/
642
- # Very reliable fingerprinting from our own esx_fingerprint module
643
- wname[$1] = wname[$1].to_i + (points * 5)
644
- wflav[$3] = wflav[$3].to_i + (points * 5)
645
- wtype['device'] = wtype['device'].to_i + points
646
- end # End of s.info for HTTPS
647
-
648
- when 'netbios'
649
- points = 201
650
- case s.info
651
- when /W2K3/i
652
- wname['Microsoft Windows 2003'] = wname['Microsoft Windows 2003'].to_i + points
653
- wtype['server'] = wtype['server'].to_i + points
654
- when /W2K8/i
655
- wname['Microsoft Windows 2008'] = wname['Microsoft Windows 2008'].to_i + points
656
- wtype['server'] = wtype['server'].to_i + points
657
- end # End of s.info for NETBIOS
658
-
659
- when 'dns'
660
- points = 101
661
- case s.info
662
- when 'Microsoft DNS'
663
- wname['Microsoft Windows'] = wname['Microsoft Windows'].to_i + points
664
- wtype['server'] = wtype['server'].to_i + points
665
- end # End of s.info for DNS
666
- end # End of s.name case
667
- # End of Services
179
+ # Normalize matches for consistency during the ranking phase
180
+ matches = matches.map{ |m| normalize_match(m) }
181
+
182
+ # Calculate the best OS match based on fingerprint hits
183
+ match = Recog::Nizer.best_os_match(matches)
184
+
185
+ # Merge and normalize the best match to the host object
186
+ apply_match_to_host(match) if match
187
+
188
+ # Handle cases where the flavor contains the base name (legacy parsing, etc)
189
+ # TODO: Remove this once we are sure it is no longer needed
190
+ if host.os_name && host.os_flavor && host.os_flavor.index(host.os_name)
191
+ dlog("Host #{host.address} has os_flavor that contains os_name")
192
+ dlog("os_flavor: #{host.os_flavor}")
193
+ dlog("os_name: #{host.os_name}")
194
+ host.os_flavor = host.os_flavor.gsub(host.os_name, '').strip
668
195
  end
669
196
 
197
+ # Set some sane defaults if needed
198
+ host.os_name ||= 'Unknown'
199
+ host.purpose ||= 'device'
200
+
201
+ host.save if host.changed?
202
+ end
203
+
204
+ def recog_matches_for_service(s)
670
205
  #
671
- # Report the best match here
206
+ # We assume that the service.info field contains certain types of probe
207
+ # replies and associate these with one or more Recog databases. The mapping
208
+ # of service.name to a specific database only fits into so many places and
209
+ # Mdm currently serves that role.
672
210
  #
673
- best_match = {}
674
- best_match[:os_name] = wname.keys.sort{|a,b| wname[b] <=> wname[a]}[0]
675
- best_match[:purpose] = wtype.keys.sort{|a,b| wtype[b] <=> wtype[a]}[0]
676
- best_match[:os_flavor] = wflav.keys.sort{|a,b| wflav[b] <=> wflav[a]}[0]
677
- best_match[:os_sp] = wvers.keys.sort{|a,b| wvers[b] <=> wvers[a]}[0]
678
- best_match[:arch] = warch.keys.sort{|a,b| warch[b] <=> warch[a]}[0]
679
- best_match[:name] = whost.keys.sort{|a,b| whost[b] <=> whost[a]}[0]
680
- best_match[:os_lang] = wlang.keys.sort{|a,b| wlang[b] <=> wlang[a]}[0]
681
-
682
- best_match[:os_flavor] ||= host[:os_flavor] || ""
683
- if best_match[:os_name]
684
- # Handle cases where the flavor contains the base name
685
- # Don't use gsub!() here because the string was a hash key in a
686
- # previously life and gets frozen on 1.9.1, see #4128
687
- best_match[:os_flavor] = best_match[:os_flavor].gsub(best_match[:os_name], '')
688
- end
689
-
690
- # If we didn't get anything, use whatever the host already has.
691
- # Failing that, fallback to "Unknown"
692
- best_match[:os_name] ||= host[:os_name] || 'Unknown'
693
- best_match[:purpose] ||= 'device'
694
-
695
- [:os_name, :purpose, :os_flavor, :os_sp, :arch, :name, :os_lang].each do |host_attr|
696
- next if host.attribute_locked? host_attr
697
- if best_match[host_attr]
698
- host[host_attr] = Rex::Text.ascii_safe_hex(best_match[host_attr])
211
+
212
+ service_match_keys = Hash.new { [] }
213
+ service_match_keys.merge({
214
+ # TODO: Implement smb.generic fingerprint database
215
+ # 'smb' => [ 'smb.generic' ], # Distinct from smb.fingerprint, use os.certainty to choose best match
216
+ # 'netbios' => [ 'smb.generic' ], # Distinct from smb.fingerprint, use os.certainty to choose best match
217
+
218
+ 'ssh' => [ 'ssh.banner' ], # Recog expects just the vendor string, not the protocol version
219
+ 'http' => [ 'http_header.server', 'apache_os'], # The 'Apache' fingerprints try to infer OS/distribution from the extra information in the Server header
220
+ 'https' => [ 'http_header.server', 'apache_os'], # XXX: verify vmware esx(i) case on https (TODO: normalize https to http, track SSL elsewhere, such as a new set of fields)
221
+ 'snmp' => [ 'snmp.sys_description' ],
222
+ 'telnet' => [ 'telnet.banner' ],
223
+ 'smtp' => [ 'smtp.banner' ],
224
+ 'imap' => [ 'imap4.banner' ], # Metasploit reports 143/993 as imap (TODO: normalize imap to imap4)
225
+ 'pop3' => [ 'pop3.banner' ], # Metasploit reports 110/995 as pop3
226
+ 'nntp' => [ 'nntp.banner' ],
227
+ 'ftp' => [ 'ftp.banner' ],
228
+ 'ssdp' => [ 'ssdp_header.server' ]
229
+ })
230
+
231
+ matches = []
232
+
233
+ service_match_keys[s.name].each do |rdb|
234
+ banner = s.info
235
+ if self.respond_to?("service_banner_recog_filter_#{s.name}")
236
+ banner = self.send("service_banner_recog_filter_#{s.name}", banner)
699
237
  end
238
+ res = Recog::Nizer.match(rdb, banner)
239
+ matches << res if res
700
240
  end
701
241
 
702
- host.save if host.changed?
242
+ matches
243
+ end
244
+
245
+ def recog_matches_for_note(note)
246
+ # Skip notes that are missing the correct structure or have been blacklisted
247
+ return [] if not validate_fingerprint_data(note)
248
+
249
+ #
250
+ # These rules define the relationship between fingerprint note keys
251
+ # and specific Recog databases for detailed matching. Notes that do
252
+ # not match a rule are passed to the generic matcher.
253
+ #
254
+ fingerprint_note_match_keys = {
255
+ 'smb.fingerprint' => {
256
+ :native_os => [ 'smb.native_os' ],
257
+ },
258
+ 'http.fingerprint' => {
259
+ :header_server => [ 'http_header.server', 'apache_os' ],
260
+ :header_set_cookie => [ 'http_header.cookie' ],
261
+ :header_www_authenticate => [ 'http_header.wwwauth' ],
262
+ # TODO: Candidates for future Recog support
263
+ # :content => 'http_body'
264
+ # :code => 'http_response_code'
265
+ # :message => 'http_response_message'
266
+ }
267
+ }
268
+
269
+ matches = []
270
+
271
+ # Look for a specific Recog database for this type and data key
272
+ if fingerprint_note_match_keys.has_key?( note.ntype )
273
+ fingerprint_note_match_keys[ note.ntype ].each_pair do |k,rdbs|
274
+ if note.data.has_key?(k)
275
+ rdbs.each do |rdb|
276
+ res = Recog::Nizer.match(rdb, note.data[k])
277
+ matches << res if res
278
+ end
279
+ end
280
+ end
281
+ else
282
+ # Add all generic match results to the overall match array
283
+ normalize_scanner_fp(note).each do |m|
284
+ next unless m
285
+ matches << m
286
+ end
287
+ end
288
+
289
+ matches
703
290
  end
704
291
 
705
292
  # Determine if the fingerprint data is readable. If not, it nearly always
@@ -720,198 +307,514 @@ module Mdm::Host::OperatingSystemNormalization
720
307
  end
721
308
  end
722
309
 
723
- protected
310
+ #
311
+ # Normalize matches in order to handle inconsistencies between fingerprint
312
+ # sources and our desired usage in Metasploit. This amounts to yet more
313
+ # duct tape, but the situation should improve as the fingerprint sources
314
+ # are updated and enhanced. In the future, this method will no longer
315
+ # be needed (or at least, doing less and less work)
316
+ #
317
+ def normalize_match(m)
318
+ # Normalize os.version strings containing 'Service Pack X' to just 'SPX'
319
+ if m['os.version'] and m['os.version'].index('Service Pack ') == 0
320
+ m['os.version'] = m['os.version'].gsub(/Service Pack /, 'SP')
321
+ end
322
+
323
+ if m['os.product']
324
+
325
+ # Normalize Apple Mac OS X to just Mac OS X
326
+ if m['os.product'] =~ /^Apple Mac/
327
+ m['os.product'] = m['os.product'].gsub(/Apple Mac/, 'Mac')
328
+ m['os.vendor'] ||= 'Apple'
329
+ end
330
+
331
+ # Normalize Sun Solaris/Sun SunOS to just Solaris/SunOS
332
+ if m['os.product'] =~ /^Sun (Solaris|SunOS)/
333
+ m['os.product'] = m['os.product'].gsub(/^Sun /, '')
334
+ m['os.vendor'] ||= 'Oracle'
335
+ end
336
+
337
+ # Normalize Microsoft Windows to just Windows to catch any stragglers
338
+ if m['os.product'] =~ /^Microsoft Windows/
339
+ m['os.product'] = m['os.product'].gsub(/Microsoft Windows/, 'Windows')
340
+ m['os.vendor'] ||= 'Microsoft'
341
+ end
342
+
343
+ # Normalize Windows Server to just Windows to match Metasploit target names
344
+ if m['os.product'] =~ /^Windows Server/
345
+ m['os.product'] = m['os.product'].gsub(/Windows Server/, 'Windows')
346
+ end
347
+ end
348
+
349
+ m
350
+ end
724
351
 
725
352
  #
726
- # Convert a host.os.*_fingerprint Note into a hash containing the standard os_* fields
353
+ # Recog assumes that the protocol version of the SSH banner has been removed
727
354
  #
728
- # Also includes a :certainty which is a float from 0 - 1.00 indicating the
729
- # scanner's confidence in its fingerprint. If the particular scanner does
730
- # not provide such information, defaults to 0.80.
355
+ def service_banner_recog_filter_ssh(banner)
356
+ if banner =~ /^SSH-\d+\.\d+-(.*)/
357
+ $1
358
+ else
359
+ banner
360
+ end
361
+ end
362
+
731
363
  #
732
- # TODO: This whole normalize scanner procedure needs to be shoved off to its own
733
- # mixin. It's far too long and convoluted, has a ton of repeated code, and is
734
- # a massive hassle to update with new fingerprints.
735
- def normalize_scanner_fp(fp)
736
- return {} if not validate_fingerprint_data(fp)
737
- ret = {}
738
- data = fp.data
739
- case fp.ntype
740
- when 'host.os.session_fingerprint'
741
- # These come from meterpreter sessions' client.sys.config.sysinfo
742
- case data[:os]
743
- when /Windows/
744
- ret.update(parse_windows_os_str(data[:os]))
745
- when /Linux (\d+\.\d+\.\d+\S*)\s* \((\w*)\)/
746
- ret[:os_name] = "Linux"
747
- ret[:name] = data[:name]
748
- ret[:os_sp] = $1
749
- ret[:arch] = get_arch_from_string($2)
750
- else
751
- ret[:os_name] = data[:os]
752
- end
753
- ret[:arch] = data[:arch] if data[:arch]
754
- ret[:name] = data[:name] if data[:name]
755
-
756
- when 'host.os.nmap_fingerprint', 'host.os.mbsa_fingerprint'
757
- # :os_vendor=>"Microsoft" :os_family=>"Windows" :os_version=>"2000" :os_accuracy=>"94"
758
- #
759
- # :os_match=>"Microsoft Windows Vista SP0 or SP1, Server 2008, or Windows 7 Ultimate (build 7000)"
760
- # :os_vendor=>"Microsoft" :os_family=>"Windows" :os_version=>"7" :os_accuracy=>"100"
761
- ret[:certainty] = data[:os_accuracy].to_f / 100.0
762
- if (data[:os_vendor] == data[:os_family])
763
- ret[:os_name] = data[:os_family]
764
- else
765
- ret[:os_name] = data[:os_vendor] + " " + data[:os_family]
766
- end
767
- ret[:os_flavor] = data[:os_version]
768
- ret[:name] = data[:hostname] if data[:hostname]
769
-
770
- when 'host.os.nexpose_fingerprint'
771
- # :family=>"Windows" :certainty=>"0.85" :vendor=>"Microsoft" :product=>"Windows 7 Ultimate Edition"
772
- # :family=>"Linux" :certainty=>"0.64" :vendor=>"Linux" :product=>"Linux"
773
- # :family=>"Linux" :certainty=>"0.80" :vendor=>"Ubuntu" :product=>"Linux"
774
- # :family=>"IOS" :certainty=>"0.80" :vendor=>"Cisco" :product=>"IOS"
775
- # :family=>"embedded" :certainty=>"0.61" :vendor=>"Linksys" :product=>"embedded"
776
- ret[:certainty] = data[:certainty].to_f
777
- case data[:family]
778
- when /AIX|ESX|Mac OS X|OpenSolaris|Solaris|IOS|Linux/
779
- if data[:vendor] == data[:family]
780
- ret[:os_name] = data[:vendor]
781
- else
782
- # family often contains the vendor string, so rip it out to
783
- # avoid useless duplication
784
- ret[:os_name] = data[:vendor].to_s + " " + data[:family].to_s.gsub(data[:vendor].to_s, '').strip
785
- end
786
- when "Windows"
787
- ret[:os_name] = "Microsoft Windows"
788
- if data[:product]
789
- if data[:product][/2008/] && data[:version].to_i == 7
790
- ret[:os_flavor] = "Windows 7"
791
- ret[:type] = "client"
792
- else
793
- ret[:os_flavor] = data[:product].gsub("Windows", '').strip
794
- ret[:os_sp] = data[:version] if data[:version]
795
- if data[:product]
796
- ret[:type] = "server" if data[:product][/Server/]
797
- ret[:type] = "client" if data[:product][/^(XP|ME)$/]
798
- end
799
- end
800
- end
801
- when "embedded"
802
- ret[:os_name] = data[:vendor]
803
- else
804
- ret[:os_name] = data[:vendor]
805
- end
806
- ret[:arch] = get_arch_from_string(data[:arch]) if data[:arch]
807
- ret[:arch] ||= get_arch_from_string(data[:desc]) if data[:desc]
808
-
809
- when 'host.os.retina_fingerprint'
810
- # :os=>"Windows Server 2003 (X64), Service Pack 2"
811
- case data[:os]
812
- when /Windows/
813
- ret.update(parse_windows_os_str(data[:os]))
814
- else
815
- # No idea what this looks like if it isn't windows. Just store
816
- # the whole thing and hope for the best. XXX: Ghetto. =/
817
- ret[:os_name] = data[:os]
364
+ # Examine the assertations of the merged best match and map these
365
+ # back to fields of {Mdm::Host}. Take particular care not to leave
366
+ # related fields (os_*) in a conflicting state, leverage existing
367
+ # values where possible, and use the most confident values we have.
368
+ #
369
+ def apply_match_to_host(match)
370
+ host = self
371
+
372
+ # These values in a match always override the current value unless
373
+ # the host attribute has been explicitly locked by the user
374
+
375
+ if match['host.mac'] && !host.attribute_locked?(:mac)
376
+ host.mac = sanitize(match['host.mac'])
377
+ end
378
+
379
+ if match['host.name'] && !host.attribute_locked?(:name)
380
+ host.name = sanitize(match['host.name'])
381
+ end
382
+
383
+ # Select the os architecture if available
384
+ if match['os.arch'] && !host.attribute_locked?(:arch)
385
+ host.arch = sanitize(match['os.arch'])
386
+ end
387
+
388
+ # Guess the purpose using some basic heuristics
389
+ if ! host.attribute_locked?(:purpose)
390
+ host.purpose = guess_purpose_from_match(match)
391
+ end
392
+
393
+ #
394
+ # Map match fields from Recog fingerprint style to Metasploit style
395
+ #
396
+
397
+ # os.build: Examples: 9001, 2600, 7602
398
+ # os.device: Examples: General, ADSL Modem, Broadband router, Cable Modem, Camera, Copier, CSU/DSU
399
+ # os.edition: Examples: Web, Storage, HPC, MultiPoint, Enterprise, Home, Starter, Professional
400
+ # os.family: Examples: Windows, Linux, Solaris, NetWare, ProCurve, Mac OS X, HP-UX, AIX
401
+ # os.product: Examples: Windows, Linux, Windows Server 2008 R2, Windows XP, Enterprise Linux, NEO Tape Library
402
+ # os.vendor: Examples: Microsoft, HP, IBM, Sun, 3Com, Ricoh, Novell, Ubuntu, Apple, Cisco, Xerox
403
+ # os.version: Examples: SP1, SP2, 6.5 SP3 CPR, 10.04, 8.04, 12.10, 4.0, 6.1, 8.5
404
+ # os.language: Examples: English, Arabic, German
405
+ # linux.kernel.version: Examples: 2.6.32
406
+
407
+ # Metasploit currently ignores os.build, os.device, and os.vendor as separate fields.
408
+
409
+ # Select the OS name from os.name, fall back to os.family
410
+ if ! host.attribute_locked?(:os_name)
411
+ # Try to fill this value from os.product first if it exists
412
+ if match.has_key?('os.product')
413
+ host.os_name = sanitize(match['os.product'])
414
+ else
415
+ # Fall back to os.family otherwise, if available
416
+ if match.has_key?('os.family')
417
+ host.os_name = sanitize(match['os.family'])
818
418
  end
819
- when 'host.os.nessus_fingerprint'
820
- # :os=>"Microsoft Windows 2000 Advanced Server (English)"
821
- # :os=>"Microsoft Windows 2000\nMicrosoft Windows XP"
822
- # :os=>"Linux Kernel 2.6"
823
- # :os=>"Sun Solaris 8"
824
- # :os=>"IRIX 6.5"
825
-
826
- # Nessus sometimes jams multiple OS names together with a newline.
827
- oses = data[:os].split(/\n/)
828
- if oses.length > 1
829
- # Multiple fingerprints means Nessus wasn't really sure, reduce
830
- # the certainty accordingly
831
- ret[:certainty] = 0.5
832
- else
833
- ret[:certainty] = 0.8
419
+ end
420
+ end
421
+
422
+ # Select the flavor from os.edition if available
423
+ if match.has_key?('os.edition') and ! host.attribute_locked?(:os_flavor)
424
+ host.os_flavor = sanitize(match['os.edition'])
425
+ end
426
+
427
+ # Select an OS version as os.version, fall back to linux.kernel.version
428
+ if ! host.attribute_locked?(:os_sp)
429
+ if match['os.version']
430
+ host.os_sp = sanitize(match['os.version'])
431
+ else
432
+ if match['linux.kernel.version']
433
+ host.os_sp = sanitize(match['linux.kernel.version'])
834
434
  end
435
+ end
436
+ end
437
+
438
+ # Select the os language if available
439
+ if match.has_key?('os.language') and ! host.attribute_locked?(:os_lang)
440
+ host.os_lang = sanitize(match['os.language'])
441
+ end
442
+
443
+ # Normalize MAC addresses to lower-case colon-delimited format
444
+ if host.mac and ! host.attribute_locked?(:mac)
445
+ host.mac = host.mac.downcase
446
+ if host.mac =~ /^[a-f0-9]{12}$/
447
+ host.mac = host.mac.scan(/../).join(':')
448
+ end
449
+ end
450
+
451
+ end
452
+
453
+ #
454
+ # Loosely guess the purpose of a device based on available
455
+ # match values. In the future, also take into account the
456
+ # exposed services and rename to guess_purpose_with_match()
457
+ #
458
+ def guess_purpose_from_match(match)
459
+ # Create a string based on all match values
460
+ pstr = match.values.join(' ').downcase
461
+
462
+ # Loosely map keywords to specific purposes
463
+ case pstr
464
+ when /windows server|windows (nt|20)/
465
+ 'server'
466
+ when /windows (xp|vista|[78])/
467
+ 'client'
468
+ when /printer|print server/
469
+ 'printer'
470
+ when /router/
471
+ 'router'
472
+ when /firewall/
473
+ 'firewall'
474
+ when /linux/
475
+ 'server'
476
+ else
477
+ 'device'
478
+ end
479
+ end
835
480
 
836
- # Since there is no confidence associated with them, the best we
837
- # can do is just take the first one.
838
- case oses.first
839
- when /Windows/
840
- ret.update(parse_windows_os_str(data[:os]))
841
-
842
- when /(2\.[46]\.\d+[-a-zA-Z0-9]+)/
843
- # Linux kernel version
844
- ret[:os_name] = "Linux"
845
- ret[:os_sp] = $1
846
- when /(.*)?((\d+\.)+\d+)$/
847
- # Then we don't necessarily know what the os is, but this
848
- # fingerprint has some version information at the end, pull it
849
- # off.
850
- # When Nessus doesn't know what kind of linux it has, it gives an os like
851
- # "Linux Kernel 2.6"
852
- # The "Kernel" string is useless, so cut it off.
853
- ret[:os_name] = $1.gsub("Kernel", '').strip
854
- ret[:os_sp] = $2
855
- else
856
- ret[:os_name] = oses.first
481
+ # Ensure that the host attribute is using ascii safe text
482
+ # and escapes any other byte value.
483
+ def sanitize(text)
484
+ Rex::Text.ascii_safe_hex(text)
485
+ end
486
+
487
+ #
488
+ # Normalize data from Meterpreter's client.sys.config.sysinfo()
489
+ #
490
+ def normalize_session_fingerprint(data)
491
+ ret = {}
492
+ case data[:os]
493
+ when /Windows/
494
+ ret.update(parse_windows_os_str(data[:os]))
495
+ # Switch to this code block once the multi-meterpreter code review is complete
496
+ =begin
497
+
498
+ when /^(Windows \w+)\s*\(Build (\d+)(.*)\)/
499
+ ret['os.product'] = $1
500
+ ret['os.build'] = $2
501
+ ret['os.vendor'] = 'Microsoft'
502
+ possible_sp = $3
503
+ if possible_sp =~ /Service Pack (\d+)/
504
+ ret['os.version'] = 'SP' + $1
857
505
  end
506
+ =end
507
+ when /Linux (\d+\.\d+\.\d+\S*)\s* \((\w*)\)/
508
+ ret['os.product'] = "Linux"
509
+ ret['os.version'] = $1
510
+ ret['os.arch'] = get_arch_from_string($2)
511
+ else
512
+ ret['os.product'] = data[:os]
513
+ end
514
+ ret['os.arch'] = data[:arch] if data[:arch]
515
+ ret['host.name'] = data[:name] if data[:name]
516
+ [ ret ]
517
+ end
518
+
519
+ #
520
+ # Normalize data from Nmap fingerprints
521
+ #
522
+ def normalize_nmap_fingerprint(data)
523
+ ret = {}
524
+
525
+ # :os_vendor=>"Microsoft" :os_family=>"Windows" :os_version=>"2000" :os_accuracy=>"94"
526
+ ret['os.certainty'] = ( data[:os_accuracy].to_f / 100.0 ).to_s if data[:os_accuracy]
527
+ if (data[:os_vendor] == data[:os_family])
528
+ ret['os.product'] = data[:os_family]
529
+ else
530
+ ret['os.product'] = data[:os_family]
531
+ ret['os.vendor'] = data[:os_vendor]
532
+ end
533
+
534
+ # Nmap places the type of Windows (XP, 7, etc) into the version field
535
+ if ret['os.product'] == 'Windows' and data[:os_version]
536
+ ret['os.product'] = ret['os.product'] + ' ' + data[:os_version].to_s
537
+ else
538
+ ret['os.version'] = data[:os_version]
539
+ end
540
+
541
+ ret['host.name'] = data[:hostname] if data[:hostname]
542
+
543
+ if ret['os.certainty']
544
+ ret['os.certainty'] = [ ret['os.certainty'].to_f, MAX_NMAP_CERTAINTY ].min.to_s
545
+ end
546
+
547
+ [ ret ]
548
+ end
549
+
550
+ #
551
+ # Normalize data from MBSA fingerprints
552
+ #
553
+ def normalize_mbsa_fingerprint(data)
554
+ ret = {}
555
+ # :os_match=>"Microsoft Windows Vista SP0 or SP1, Server 2008, or Windows 7 Ultimate (build 7000)"
556
+ # :os_vendor=>"Microsoft" :os_family=>"Windows" :os_version=>"7" :os_accuracy=>"100"
557
+ ret['os.certainty'] = ( data[:os_accuracy].to_f / 100.0 ).to_s if data[:os_accuracy]
558
+ ret['os.family'] = data[:os_family] if data[:os_family]
559
+ ret['os.vendor'] = data[:os_vendor] if data[:os_vendor]
560
+
561
+ if data[:os_family] and data[:os_version]
562
+ ret['os.product'] = data[:os_family] + " " + data[:os_version]
563
+ end
564
+
565
+ ret['host.name'] = data[:hostname] if data[:hostname]
566
+
567
+ [ ret ]
568
+ end
569
+
570
+
571
+ #
572
+ # Normalize data from Nexpose fingerprints
573
+ #
574
+ def normalize_nexpose_fingerprint(data)
575
+ ret = {}
576
+ # :family=>"Windows" :certainty=>"0.85" :vendor=>"Microsoft" :product=>"Windows 7 Ultimate Edition"
577
+ # :family=>"Windows" :certainty=>"0.67" :vendor=>"Microsoft" :arch=>"x86" :product=>'Windows 7' :version=>'SP1'
578
+ # :family=>"Linux" :certainty=>"0.64" :vendor=>"Linux" :product=>"Linux"
579
+ # :family=>"Linux" :certainty=>"0.80" :vendor=>"Ubuntu" :product=>"Linux"
580
+ # :family=>"IOS" :certainty=>"0.80" :vendor=>"Cisco" :product=>"IOS"
581
+ # :family=>"embedded" :certainty=>"0.61" :vendor=>"Linksys" :product=>"embedded"
582
+
583
+ ret['os.certainty'] = data[:certainty] if data[:certainty]
584
+ ret['os.family'] = data[:family] if data[:family]
585
+ ret['os.vendor'] = data[:vendor] if data[:vendor]
586
+
587
+ case data[:product]
588
+ when /^Windows/
589
+
590
+ # TODO: Verify Windows CE and Windows 8 RT fingerprints
591
+ # Translate the version into the representation we want
592
+
593
+ case data[:version].to_s
594
+
595
+ # These variants are normalized to just 'Windows <Version>'
596
+ when "NT", "2000", "95", "ME", "XP", "Vista", "7", "8", "8.1"
597
+ ret['os.product'] = "Windows #{data[:version]}"
598
+
599
+ # Service pack in the version field should be recognized
600
+ when /^SP\d+/, /^Service Pack \d+/
601
+ ret['os.product'] = data[:product]
602
+ ret['os.version'] = data[:version]
603
+
604
+ # No version means the version is part of the product already
605
+ when nil, ''
606
+ # Trim any 'Server' suffix and use as it is
607
+ ret['os.product'] = data[:product].sub(/ Server$/, '')
608
+
609
+ # Otherwise, we assume a Server version of Windows
610
+ else
611
+ ret['os.product'] = "Windows Server #{data[:version]}"
612
+ end
613
+
614
+ # Extract the edition string if it is present
615
+ if data[:product] =~ /(XP|Vista|\d+(?:\.\d+)) (\w+|\w+ \w+|\w+ \w+ \w+) Edition/
616
+ ret['os.edition'] = $2
617
+ end
618
+
619
+ when nil, 'embedded'
620
+ # Use the family or vendor name when the product is empty or 'embedded'
621
+ ret['os.product'] = data[:family] unless data[:family] == 'embedded'
622
+ ret['os.product'] ||= data[:vendor]
623
+ ret['os.version'] = data[:version] if data[:version]
624
+ else
625
+ # Default to using the product name reported by Nexpose
626
+ ret['os.product'] = data[:product] if data[:product]
627
+ end
628
+
629
+ ret['os.arch'] = get_arch_from_string(data[:arch]) if data[:arch]
630
+ ret['os.arch'] ||= get_arch_from_string(data[:desc]) if data[:desc]
631
+
632
+ [ ret ]
633
+ end
634
+
635
+
636
+ #
637
+ # Normalize data from Retina fingerprints
638
+ #
639
+ def normalize_retina_fingerprint(data)
640
+ ret = {}
641
+ # :os=>"Windows Server 2003 (X64), Service Pack 2"
642
+ case data[:os]
643
+ when /Windows/
644
+ ret.update(parse_windows_os_str(data[:os]))
645
+ else
646
+ # No idea what this looks like if it isn't windows. Just store
647
+ # the whole thing and hope for the best.
648
+ # TODO: Add examples of non-Windows results
649
+ ret['os.product'] = data[:os] if data[:os]
650
+ end
651
+ [ ret ]
652
+ end
653
+
654
+
655
+ #
656
+ # Normalize data from Nessus fingerprints
657
+ #
658
+ def normalize_nessus_fingerprint(data)
659
+ ret = {}
660
+ # :os=>"Microsoft Windows 2000 Advanced Server (English)"
661
+ # :os=>"Microsoft Windows 2000\nMicrosoft Windows XP"
662
+ # :os=>"Linux Kernel 2.6"
663
+ # :os=>"Sun Solaris 8"
664
+ # :os=>"IRIX 6.5"
665
+
666
+ # Nessus sometimes jams multiple OS names together with a newline.
667
+ oses = data[:os].split(/\n/)
668
+ if oses.length > 1
669
+ # Multiple fingerprints means Nessus wasn't really sure, reduce
670
+ # the certainty accordingly
671
+ ret['os.certainty'] = 0.5
672
+ else
673
+ ret['os.certainty'] = 0.8
674
+ end
858
675
 
859
- ret[:name] = data[:hname]
860
- when 'host.os.qualys_fingerprint'
861
- # :os=>"Microsoft Windows 2000"
862
- # :os=>"Windows 2003"
863
- # :os=>"Microsoft Windows XP Professional SP3"
864
- # :os=>"Ubuntu Linux"
865
- # :os=>"Cisco IOS 12.0(3)T3"
866
- case data[:os]
867
- when /Windows/
868
- ret.update(parse_windows_os_str(data[:os]))
869
- else
870
- parts = data[:os].split(/\s+/, 3)
871
- ret[:os_name] = "<unknown>"
872
- ret[:os_name] = parts[0] if parts[0]
873
- ret[:os_name] << " " + parts[1] if parts[1]
874
- ret[:os_sp] = parts[2] if parts[2]
676
+ # Since there is no confidence associated with them, the best we
677
+ # can do is just take the first one.
678
+ case oses.first
679
+ when /^(Microsoft |)Windows/
680
+ ret.update(parse_windows_os_str(data[:os]))
681
+
682
+ when /(2\.[46]\.\d+[-a-zA-Z0-9]+)/
683
+ # Look for older Linux kernel versions
684
+ ret['os.product'] = "Linux"
685
+ ret['os.version'] = $1
686
+
687
+ when /^Linux Kernel ([\d\.]+)(.*)/
688
+ # Look for strings like "Linux Kernel 2.6 on Ubuntu 9.10 (karmic)"
689
+ # Ex: Linux Kernel 2.2 on Red Hat Linux release 6.2 (Zoot)
690
+ # Ex: Linux Kernel 2.6 on Ubuntu Linux 8.04 (hardy)
691
+ ret['os.product'] = "Linux"
692
+ ret['os.version'] = $1
693
+
694
+ vendor = $2.to_s
695
+
696
+ # Try to snag the vendor name as well
697
+ if vendor =~ /on (\w+|\w+ \w+|\w+ \w+ \w+) (Linux|\d)/
698
+ ret['os.vendor'] = $1
875
699
  end
876
- # XXX: We should really be using smb_version's stored fingerprints
877
- # instead of parsing the service info manually. Disable for now so we
878
- # don't count smb twice.
879
- #when 'smb.fingerprint'
880
- # # smb_version is kind enough to store everything we need directly
881
- # ret.merge(fp.data)
882
- # # If it's windows, this should be a pretty high-confidence
883
- # # fingerprint. Otherwise, it's samba which doesn't give us much of
884
- # # anything in most cases.
885
- # ret[:certainty] = 1.0 if fp.data[:os_name] =~ /Windows/
886
- when 'host.os.fusionvm_fingerprint'
887
- case data[:os]
888
- when /Windows/
889
- ret.update(parse_windows_os_str(data[:os]))
890
- when /Linux ([^[:space:]]*) ([^[:space:]]*) .* (\(.*\))/
891
- ret[:os_name] = "Linux"
892
- ret[:name] = $1
893
- ret[:os_sp] = $2
894
- ret[:arch] = get_arch_from_string($3)
895
- else
896
- ret[:os_name] = data[:os]
700
+
701
+ when /(.*) ([0-9\.]+)$/
702
+ # Then we don't necessarily know what the os is, but this fingerprint has
703
+ # some version information at the end, pull it off, treat the first part
704
+ # as the OS, and the rest as the version.
705
+ ret['os.product'] = $1.gsub("Kernel", '').strip
706
+ ret['os.version'] = $2
707
+ else
708
+ # TODO: Return each OS guess as a separate match
709
+ ret['os.product'] = oses.first
710
+ end
711
+
712
+ ret['host.name'] = data[:hname] if data[:hname]
713
+ [ ret ]
714
+ end
715
+
716
+ #
717
+ # Normalize data from Qualys fingerprints
718
+ #
719
+ def normalize_qualys_fingerprint(data)
720
+ ret = {}
721
+ # :os=>"Microsoft Windows 2000"
722
+ # :os=>"Windows 2003"
723
+ # :os=>"Microsoft Windows XP Professional SP3"
724
+ # :os=>"Ubuntu Linux"
725
+ # :os=>"Cisco IOS 12.0(3)T3"
726
+ # :os=>"Red-Hat Linux 6.0"
727
+ case data[:os]
728
+ when /Windows/
729
+ ret.update(parse_windows_os_str(data[:os]))
730
+
731
+ when /^(Cisco) (IOS) (\d+[^\s]+)/
732
+ ret['os.product'] = $2
733
+ ret['os.vendor'] = $1
734
+ ret['os.version'] = $3
735
+
736
+ when /^([^\s]+) (Linux)(.*)/
737
+ ret['os.product'] = $2
738
+ ret['os.vendor'] = $1
739
+
740
+ ver = $3.to_s.strip.split(/\s+/).first
741
+ if ver =~ /^\d+\./
742
+ ret['os.version'] = ver
897
743
  end
898
- ret[:arch] = data[:arch] if data[:arch]
899
- ret[:name] = data[:name] if data[:name]
744
+
900
745
  else
901
- # If you've fallen through this far, you've hit a generalized
902
- # pass-through fingerprint parser.
903
- ret[:os_name] = data[:os_name] || data[:os] || data[:os_fingerprint] || "<unknown>"
904
- ret[:type] = data[:os_purpose] if data[:os_purpose]
905
- ret[:arch] = data[:os_arch] if data[:os_arch]
906
- ret[:certainty] = data[:os_certainty] || 0.5
907
- end
908
- ret[:certainty] ||= 0.8
909
- ret
746
+ parts = data[:os].split(/\s+/, 3)
747
+ ret['os.product'] = "Unknown"
748
+ ret['os.product'] = parts[0] if parts[0]
749
+ ret['os.product'] << " " + parts[1] if parts[1]
750
+ ret['os.version'] = parts[2] if parts[2]
751
+ end
752
+ [ ret ]
753
+ end
754
+
755
+ #
756
+ # Normalize data from FusionVM fingerprints
757
+ #
758
+ def normalize_fusionvm_fingerprint(data)
759
+ ret = {}
760
+ case data[:os]
761
+ when /Windows/
762
+ ret.update(parse_windows_os_str(data[:os]))
763
+ when /Linux ([^[:space:]]*) ([^[:space:]]*) .* (\(.*\))/
764
+ ret['os.product'] = "Linux"
765
+ ret['host.name'] = $1
766
+ ret['os.version'] = $2
767
+ ret['os.arch'] = get_arch_from_string($3)
768
+ else
769
+ ret['os.product'] = data[:os]
770
+ end
771
+ ret['os.arch'] = data[:arch] if data[:arch]
772
+ ret['host.name'] = data[:name] if data[:name]
773
+ [ ret ]
774
+ end
775
+
776
+ #
777
+ # Normalize data from generic fingerprints
778
+ #
779
+ def normalize_generic_fingerprint(data)
780
+ ret = {}
781
+ ret['os.product'] = data[:os_name] || data[:os] || data[:os_fingerprint] || "Unknown"
782
+ ret['os.arch'] = data[:os_arch] if data[:os_arch]
783
+ ret['os.certainty'] = data[:os_certainty] || 0.5
784
+ [ ret ]
785
+ end
786
+
787
+ #
788
+ # Convert a host.os.*_fingerprint Note into a hash containing 'os.*' and 'host.*' fields
789
+ #
790
+ # Also includes a os.certainty which is a float from 0 - 1.00 indicating the
791
+ # scanner's confidence in its fingerprint. If the particular scanner does
792
+ # not provide such information, default to 0.80.
793
+ #
794
+ def normalize_scanner_fp(fp)
795
+ hits = []
796
+
797
+ return hits if not validate_fingerprint_data(fp)
798
+
799
+ case fp.ntype
800
+ when /^host\.os\.(.*_fingerprint)$/
801
+ pname = $1
802
+ pmeth = 'normalize_' + pname
803
+ if self.respond_to?(pmeth)
804
+ hits = self.send(pmeth, fp.data)
805
+ else
806
+ hits = normalize_generic_fingerprint(fp.data)
807
+ end
808
+ end
809
+ hits.each {|hit| hit['os.certainty'] ||= 0.80}
810
+ hits
910
811
  end
911
812
 
912
813
  #
913
814
  # Take a windows version string and return a hash with fields suitable for
914
- # Host this object's version fields.
815
+ # Host this object's version fields. This is used as a fall-back to parse
816
+ # external fingerprints and should eventually be replaced by per-source
817
+ # mappings.
915
818
  #
916
819
  # A few example strings that this will have to parse:
917
820
  # sessions
@@ -933,52 +836,64 @@ module Mdm::Host::OperatingSystemNormalization
933
836
  def parse_windows_os_str(str)
934
837
  ret = {}
935
838
 
936
- ret[:os_name] = "Microsoft Windows"
937
- arch = get_arch_from_string(str)
938
- ret[:arch] = arch if arch
839
+ # Set some reasonable defaults for Windows
840
+ ret['os.vendor'] = 'Microsoft'
841
+ ret['os.product'] = 'Windows'
939
842
 
940
- if str =~ /(Service Pack|SP) ?(\d+)/
941
- ret[:os_sp] = "SP#{$2}"
942
- end
943
-
944
- # Flavor
843
+ # Determine the actual Windows product name
945
844
  case str
946
845
  when /\.NET Server/
947
- ret[:os_flavor] = "2003"
948
- when /(XP|2000 Advanced Server|2000|2003|2008|SBS|Vista|7 .* Edition|7)/
949
- ret[:os_flavor] = $1
846
+ ret['os.product'] << ' Server 2003'
847
+ when / (2000|2003|2008|2012)/
848
+ ret['os.product'] << ' Server ' + $1
849
+ when / (NT (?:3\.51|4\.0))/
850
+ ret['os.product'] << ' ' + $1
851
+ when /Windows (95|98|ME|XP|Vista|[\d\.]+)/
852
+ ret['os.product'] << ' ' + $1
950
853
  else
951
854
  # If we couldn't pull out anything specific for the flavor, just cut
952
855
  # off the stuff we know for sure isn't it and hope for the best
953
- ret[:os_flavor] ||= str.gsub(/(Microsoft )?Windows|(Service Pack|SP) ?(\d+)/, '').strip
856
+ ret['os.product'] = (ret['os.product'] + ' ' + str.gsub(/(Microsoft )|(Windows )|(Service Pack|SP) ?(\d+)/i, '').strip).strip
857
+
858
+ # Make sure the product name doesn't include any non-alphanumeric stuff
859
+ # This fixes cases where the above code leaves 'Windows XX (Build 3333,)...'
860
+ ret['os.product'] = ret['os.product'].split(/[^a-zA-Z0-9 ]/).first.strip
861
+
862
+ end
863
+
864
+ # Take a guess at the architecture
865
+ arch = get_arch_from_string(str)
866
+ ret['os.arch'] = arch if arch
867
+
868
+ # Extract any service pack value in the string
869
+ if str =~ /(Service Pack|SP) ?(\d+)/i
870
+ ret['os.version'] = "SP#{$2}"
871
+ end
872
+
873
+ # Extract any build ID found in the string
874
+ if str =~ /build (\d+)/i
875
+ ret['os.build'] = $1
954
876
  end
955
877
 
956
- if str =~ /NT|2003|2008|SBS|Server/
957
- ret[:type] = 'server'
878
+ # Extract the OS edition if available
879
+ if str =~ /(\d+|\d+\.\d+) (\w+|\w+ \w+|\w+ \w+ \w+) Edition/
880
+ ret['os.edition'] = $2
958
881
  else
959
- ret[:type] = 'client'
882
+ if str =~ /(Professional|Enterprise|Pro|Home|Start|Datacenter|Web|Storage|MultiPoint)/
883
+ ret['os.edition'] = $1
884
+ end
960
885
  end
961
886
 
962
887
  ret
963
888
  end
964
889
 
965
- # A case switch to return a normalized arch based on a given string.
890
+ #
891
+ # Return a normalized architecture based on patterns in the input string.
892
+ # This will identify things like sparc, powerpc, x86_x64, and i686
893
+ #
966
894
  def get_arch_from_string(str)
967
- case str
968
- when /x64|amd64|x86_64/i
969
- "x64"
970
- when /x86|i[3456]86/i
971
- "x86"
972
- when /PowerPC|PPC|POWER|ppc/
973
- "ppc"
974
- when /SPARC/i
975
- "sparc"
976
- when /MIPS/i
977
- "mips"
978
- when /ARM/i
979
- "arm"
980
- else
981
- nil
982
- end
895
+ res = Recog::Nizer.match("architecture", str)
896
+ return unless (res and res['os.arch'])
897
+ res['os.arch']
983
898
  end
984
- end
899
+ end