metasploit_data_models 0.20.1.pre.recog → 0.20.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- ZjljYTMwZjc5NzZhODJmNmYwMzk5NDI2YjJhODEwZTZkNGIzNWJmMQ==
4
+ ZmM0ZmRiYjg4NWVhNWI5NzY1ZDk4Y2JjMTQwMGJiNTVmMmNhODVjZQ==
5
5
  data.tar.gz: !binary |-
6
- ZDIwMzU5MTdhMjRmZGI3MDIyYTdmODk0ZjBkMjlhNTc1NWMwMThlOA==
6
+ MmFmNzA2YTJjYjZkNjI3ZjBlNGNhYzQxOTU4N2MwMzA2MDdiNjUwOA==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- ZThkZjcyMDg4OTZiMmY5ZGY0NzQzZmE4M2E4OGEwNzIxZjJlZGE3OTYzNmMx
10
- OTIzYjcxOTBlNzE3YmQ4NjIzMTk1NzQyN2U1NDk5YTY3OWZkZTllMTMwZGM1
11
- Zjg1OGYwMjBlMDcxNjIxNDU3MjAxYjI5ODE5NjgzNmE1ZDA5Mjg=
9
+ ZTlkNjNkMjE0ZmU4ZWRhY2MwZWIyMmE3M2M3YzRhZjg3OTg2MmU1YmY3MmJj
10
+ NWNmNTk4MWM0MzM5ZGRjZTg4OGFiYWMwNTMzOWY0ZGMwNWMwZjgwZjc3ODI3
11
+ NjdkNWExZThkNGE0MzY2ZTkwZGJlYTcxOTlhMjEyZWQ4ODFmNTA=
12
12
  data.tar.gz: !binary |-
13
- YzI5NWU3MjNkZjI4ZWEzYzI3N2IzYTM1YmRjOWU5NTIxYjNmMmRmMTE4ZGFm
14
- MDllYjNhYzY1NThkMDQwNDA4NzlmNjY0ZWY4YWQ3NTFkMGE2ODI3ODA3MmNh
15
- NmQ4YWZjYjQ1Yjc4YzI2MzM4YmJiMDllNjdiNDBlOWM1YWNmZGM=
13
+ ZmE4YzgzZjUwNDM5YjJjMzUwOGYwYjk0NzJhYTNmZDkzNDA1ZjhlOGZjMTBl
14
+ NjJlZDlmZjlmNTNlZDlmOWQ5YTM0MmQ4MzRlZjQyYjVkMGQ5NWIxYzZhMWRm
15
+ ZWJiMDRiNDY2Nzk0MmI2ZmI3ZGJkZmE2ZTk3MTY2M2NiNDJkMjE=
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source "https://rubygems.org"
1
+ source "http://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in metasploit_data_models.gemspec
4
4
  gemspec
@@ -10,8 +10,6 @@ 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
15
13
  # supplies factories for producing model instance for specs
16
14
  # Version 4.1.0 or newer is needed to support generate calls without the 'FactoryGirl.' in factory definitions syntax.
17
15
  gem 'factory_girl', '>= 4.1.0'
@@ -26,6 +24,8 @@ group :development, :test do
26
24
  end
27
25
 
28
26
  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 = 'Windows'
362
+ # host.os_name = 'Microsoft 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 = 'Windows'
382
+ # host.os_name = 'Microsoft Windows'
383
383
  # host.os_flavor = 'XP'
384
384
  # host.os_sp = 'SP2'
385
385
  #
@@ -1,292 +1,705 @@
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
- #
130
1
  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
-
138
2
  #
139
3
  # Normalize the operating system fingerprints provided by various scanners
140
- # (nmap, nexpose, retina, nessus, metasploit modules, and more!)
4
+ # (nmap, nexpose, retina, nessus, etc).
141
5
  #
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}.
6
+ # These are stored as notes (instead of directly in the os_* fields)
7
+ # specifically for this purpose.
150
8
  #
151
9
  def normalize_os
152
- host = self
153
- matches = []
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
154
19
 
155
20
  # Note that we're already restricting the query to this host by using
156
21
  # host.notes instead of Note, so don't need a host_id in the
157
22
  # conditions.
158
23
  fingerprintable_notes = self.notes.where("ntype like '%%fingerprint'")
159
- fingerprintable_notes.each do |fp_note|
160
- matches += recog_matches_for_note(fp_note)
161
- end
162
-
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.
163
47
  # XXX: This hack solves the memory leak generated by self.services.each {}
164
48
  fingerprintable_services = self.services.where("name is not null and name != '' and info is not null and info != ''")
165
49
  fingerprintable_services.each do |s|
166
- matches += recog_matches_for_service(s)
167
- end
168
-
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
178
-
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)
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
184
120
 
185
- # Merge and normalize the best match to the host object
186
- apply_match_to_host(match) if match
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
187
335
 
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
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
195
668
  end
196
669
 
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)
205
670
  #
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.
671
+ # Report the best match here
210
672
  #
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)
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])
237
699
  end
238
- res = Recog::Nizer.match(rdb, banner)
239
- matches << res if res
240
700
  end
241
701
 
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
702
+ host.save if host.changed?
290
703
  end
291
704
 
292
705
  # Determine if the fingerprint data is readable. If not, it nearly always
@@ -307,514 +720,198 @@ module Mdm::Host::OperatingSystemNormalization
307
720
  end
308
721
  end
309
722
 
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']
723
+ protected
324
724
 
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
351
-
352
- #
353
- # Recog assumes that the protocol version of the SSH banner has been removed
354
725
  #
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
-
726
+ # Convert a host.os.*_fingerprint Note into a hash containing the standard os_* fields
363
727
  #
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.
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.
368
731
  #
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'])
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]
418
752
  end
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'])
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]
434
766
  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
480
-
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
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]
505
805
  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
675
-
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
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]
699
818
  end
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
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
743
834
  end
744
835
 
745
- else
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)
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
857
+ end
798
858
 
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)
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]
875
+ 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]
897
+ end
898
+ ret[:arch] = data[:arch] if data[:arch]
899
+ ret[:name] = data[:name] if data[:name]
805
900
  else
806
- hits = normalize_generic_fingerprint(fp.data)
807
- end
808
- end
809
- hits.each {|hit| hit['os.certainty'] ||= 0.80}
810
- hits
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
811
910
  end
812
911
 
813
912
  #
814
913
  # Take a windows version string and return a hash with fields suitable for
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.
914
+ # Host this object's version fields.
818
915
  #
819
916
  # A few example strings that this will have to parse:
820
917
  # sessions
@@ -836,64 +933,52 @@ module Mdm::Host::OperatingSystemNormalization
836
933
  def parse_windows_os_str(str)
837
934
  ret = {}
838
935
 
839
- # Set some reasonable defaults for Windows
840
- ret['os.vendor'] = 'Microsoft'
841
- ret['os.product'] = 'Windows'
936
+ ret[:os_name] = "Microsoft Windows"
937
+ arch = get_arch_from_string(str)
938
+ ret[:arch] = arch if arch
842
939
 
843
- # Determine the actual Windows product name
940
+ if str =~ /(Service Pack|SP) ?(\d+)/
941
+ ret[:os_sp] = "SP#{$2}"
942
+ end
943
+
944
+ # Flavor
844
945
  case str
845
946
  when /\.NET Server/
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
947
+ ret[:os_flavor] = "2003"
948
+ when /(XP|2000 Advanced Server|2000|2003|2008|SBS|Vista|7 .* Edition|7)/
949
+ ret[:os_flavor] = $1
853
950
  else
854
951
  # If we couldn't pull out anything specific for the flavor, just cut
855
952
  # off the stuff we know for sure isn't it and hope for the best
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
953
+ ret[:os_flavor] ||= str.gsub(/(Microsoft )?Windows|(Service Pack|SP) ?(\d+)/, '').strip
876
954
  end
877
955
 
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
956
+ if str =~ /NT|2003|2008|SBS|Server/
957
+ ret[:type] = 'server'
881
958
  else
882
- if str =~ /(Professional|Enterprise|Pro|Home|Start|Datacenter|Web|Storage|MultiPoint)/
883
- ret['os.edition'] = $1
884
- end
959
+ ret[:type] = 'client'
885
960
  end
886
961
 
887
962
  ret
888
963
  end
889
964
 
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
- #
965
+ # A case switch to return a normalized arch based on a given string.
894
966
  def get_arch_from_string(str)
895
- res = Recog::Nizer.match("architecture", str)
896
- return unless (res and res['os.arch'])
897
- res['os.arch']
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
898
983
  end
899
- end
984
+ end