metasploit_data_models 0.21.0 → 0.21.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- YjVkNWJlNDA0YmRjNWRmODM4ODY0YWEyZWI3YzlhZjJjODNmMmYwZg==
4
+ YjcyNDEyZmVhZDIwMWFkYmM2ODg0NzhkY2IyNzQyOGVkNjU1ODNjMw==
5
5
  data.tar.gz: !binary |-
6
- NmU4NTBlOTQwNzQ3MzNkYzBiYmY0NmFhM2I3ZWMyNDIxMjk3YTM4MA==
6
+ M2U0ZjMwMzdkZTgwZmQ4YTBhNDVjNTY2OTVjMzIxY2M5NjRhMWZlZg==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- ZGVkZTU5MGM0NDM2ZDJlM2UyNzlmNGJiMjJiZmQ5MDEyMmY3YzM1YThmM2Y2
10
- MmY4YjNiNmM2MTEyOWQzMDMyYjBlNDQ3NTlkN2IwN2M5YmM4NDdlYmE1ODNi
11
- ZmYwNDdmY2MxNmIxZjlkNTdkNjA1YmE5ZGMxN2JkMjBmNTk3N2Q=
9
+ Y2ExMGI1YjgxZTBmY2IxNzVlZTYyMDRlNWVmZGFhZTcxMWNiMDc0MDk4NTJm
10
+ ODBkYjRkNmQ2NGYzNWY1MTk4YTViNDEyZGQ1NDkzZjE1ODQ4ZWJiMjU5M2Fl
11
+ ZDg2YThmMTFlZDlkN2NlZWQ1MGI4ZDJlMjM2MDI1NTM4ZGY2ZDE=
12
12
  data.tar.gz: !binary |-
13
- NDlkYTRjMzNkMzU4OWFhMjMxY2Q1N2YyN2JmZjljNGY1YzNiODk1OWNlMjg4
14
- ZDg0MjE3YTRlNDZhNDY1MzJmZTc1OWE4ZWFlZTdhZTMxYTljNDVhNzI3ZTEz
15
- MGI3NzU0NDRhYTM3MWNmM2JjNDMzMmYwNjM0YzM0NDkyZjhjODA=
13
+ NWJhNzAxMzdkYmQ4MWVkOGUyNDJkYTIxZDQ4NjJjZDQxYzQwNzM5NDdiNWE4
14
+ Yzg2YjQwMzU1ZDhhM2I4YzZlYWE2Y2I4MWFhNWVjOGMyZjFhZTE5YmMyZjEy
15
+ Zjk5MjQ4NTQ1YTMzODliNDU3YTNjZjlmMzc1NjdkY2QxZjA5OTk=
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.has_key?('host.mac') and ! host.attribute_locked?(:mac)
376
+ host.mac = sanitize(match['host.mac'])
377
+ end
378
+
379
+ if match.has_key?('host.name') and ! host.attribute_locked?(:name)
380
+ host.name = sanitize(match['host.name'])
381
+ end
382
+
383
+ # Select the os architecture if available
384
+ if match.has_key?('os.arch') and ! 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