recog-intrigue 2.3.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (130) hide show
  1. checksums.yaml +7 -0
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +37 -0
  3. data/.github/ISSUE_TEMPLATE/feature_request.md +17 -0
  4. data/.github/ISSUE_TEMPLATE/fingerprint_request.md +27 -0
  5. data/.github/PULL_REQUEST_TEMPLATE +24 -0
  6. data/.gitignore +14 -0
  7. data/.rbenv-gemset +1 -0
  8. data/.rspec +3 -0
  9. data/.ruby-gemset +1 -0
  10. data/.ruby-version +1 -0
  11. data/.travis.yml +25 -0
  12. data/.yardopts +1 -0
  13. data/CONTRIBUTING.md +171 -0
  14. data/COPYING +23 -0
  15. data/Gemfile +10 -0
  16. data/LICENSE +7 -0
  17. data/README.md +85 -0
  18. data/Rakefile +22 -0
  19. data/bin/recog_export +81 -0
  20. data/bin/recog_match +55 -0
  21. data/bin/recog_standardize +118 -0
  22. data/bin/recog_verify +64 -0
  23. data/cpe-remap.yaml +134 -0
  24. data/features/data/failing_banners_fingerprints.xml +20 -0
  25. data/features/data/matching_banners_fingerprints.xml +23 -0
  26. data/features/data/multiple_banners_fingerprints.xml +32 -0
  27. data/features/data/no_tests.xml +3 -0
  28. data/features/data/sample_banner.txt +2 -0
  29. data/features/data/successful_tests.xml +18 -0
  30. data/features/data/tests_with_failures.xml +20 -0
  31. data/features/data/tests_with_warnings.xml +17 -0
  32. data/features/match.feature +36 -0
  33. data/features/support/aruba.rb +3 -0
  34. data/features/support/env.rb +6 -0
  35. data/features/verify.feature +48 -0
  36. data/identifiers/README.md +47 -0
  37. data/identifiers/os_architecture.txt +20 -0
  38. data/identifiers/os_device.txt +52 -0
  39. data/identifiers/os_family.txt +160 -0
  40. data/identifiers/os_product.txt +199 -0
  41. data/identifiers/service_family.txt +185 -0
  42. data/identifiers/service_product.txt +255 -0
  43. data/identifiers/software_class.txt +26 -0
  44. data/identifiers/software_family.txt +91 -0
  45. data/identifiers/software_product.txt +333 -0
  46. data/identifiers/vendor.txt +405 -0
  47. data/lib/recog.rb +4 -0
  48. data/lib/recog/db.rb +78 -0
  49. data/lib/recog/db_manager.rb +31 -0
  50. data/lib/recog/fingerprint.rb +280 -0
  51. data/lib/recog/fingerprint/regexp_factory.rb +56 -0
  52. data/lib/recog/fingerprint/test.rb +18 -0
  53. data/lib/recog/formatter.rb +51 -0
  54. data/lib/recog/match_reporter.rb +77 -0
  55. data/lib/recog/matcher.rb +94 -0
  56. data/lib/recog/matcher_factory.rb +14 -0
  57. data/lib/recog/nizer.rb +347 -0
  58. data/lib/recog/verifier.rb +39 -0
  59. data/lib/recog/verifier_factory.rb +13 -0
  60. data/lib/recog/verify_reporter.rb +86 -0
  61. data/lib/recog/version.rb +3 -0
  62. data/misc/convert_mysql_err +61 -0
  63. data/misc/order.xsl +17 -0
  64. data/recog-intrigue.gemspec +45 -0
  65. data/requirements.txt +2 -0
  66. data/spec/data/best_os_match_1.yml +17 -0
  67. data/spec/data/best_os_match_2.yml +17 -0
  68. data/spec/data/best_service_match_1.yml +17 -0
  69. data/spec/data/smb_native_os.txt +25 -0
  70. data/spec/data/test_fingerprints.xml +36 -0
  71. data/spec/data/verification_fingerprints.xml +86 -0
  72. data/spec/data/whitespaced_fingerprint.xml +5 -0
  73. data/spec/lib/fingerprint_self_test_spec.rb +174 -0
  74. data/spec/lib/recog/db_spec.rb +98 -0
  75. data/spec/lib/recog/fingerprint/regexp_factory_spec.rb +73 -0
  76. data/spec/lib/recog/fingerprint_spec.rb +112 -0
  77. data/spec/lib/recog/formatter_spec.rb +69 -0
  78. data/spec/lib/recog/match_reporter_spec.rb +91 -0
  79. data/spec/lib/recog/nizer_spec.rb +330 -0
  80. data/spec/lib/recog/verify_reporter_spec.rb +113 -0
  81. data/spec/spec_helper.rb +82 -0
  82. data/update_cpes.py +186 -0
  83. data/xml/apache_modules.xml +1911 -0
  84. data/xml/apache_os.xml +273 -0
  85. data/xml/architecture.xml +36 -0
  86. data/xml/dns_versionbind.xml +761 -0
  87. data/xml/fingerprints.xsd +128 -0
  88. data/xml/ftp_banners.xml +1553 -0
  89. data/xml/h323_callresp.xml +603 -0
  90. data/xml/hp_pjl_id.xml +358 -0
  91. data/xml/html_title.xml +1630 -0
  92. data/xml/http_cookies.xml +411 -0
  93. data/xml/http_servers.xml +3195 -0
  94. data/xml/http_wwwauth.xml +595 -0
  95. data/xml/imap_banners.xml +245 -0
  96. data/xml/ldap_searchresult.xml +711 -0
  97. data/xml/mdns_device-info_txt.xml +1796 -0
  98. data/xml/mdns_workstation_txt.xml +15 -0
  99. data/xml/mysql_banners.xml +1649 -0
  100. data/xml/mysql_error.xml +871 -0
  101. data/xml/nntp_banners.xml +82 -0
  102. data/xml/ntp_banners.xml +1223 -0
  103. data/xml/operating_system.xml +629 -0
  104. data/xml/pop_banners.xml +499 -0
  105. data/xml/rsh_resp.xml +76 -0
  106. data/xml/rtsp_servers.xml +76 -0
  107. data/xml/sip_banners.xml +359 -0
  108. data/xml/sip_user_agents.xml +221 -0
  109. data/xml/smb_native_lm.xml +62 -0
  110. data/xml/smb_native_os.xml +662 -0
  111. data/xml/smtp_banners.xml +1690 -0
  112. data/xml/smtp_debug.xml +39 -0
  113. data/xml/smtp_ehlo.xml +49 -0
  114. data/xml/smtp_expn.xml +82 -0
  115. data/xml/smtp_help.xml +157 -0
  116. data/xml/smtp_mailfrom.xml +20 -0
  117. data/xml/smtp_noop.xml +44 -0
  118. data/xml/smtp_quit.xml +29 -0
  119. data/xml/smtp_rcptto.xml +25 -0
  120. data/xml/smtp_rset.xml +26 -0
  121. data/xml/smtp_turn.xml +26 -0
  122. data/xml/smtp_vrfy.xml +89 -0
  123. data/xml/snmp_sysdescr.xml +6507 -0
  124. data/xml/snmp_sysobjid.xml +430 -0
  125. data/xml/ssh_banners.xml +1968 -0
  126. data/xml/telnet_banners.xml +1595 -0
  127. data/xml/x11_banners.xml +232 -0
  128. data/xml/x509_issuers.xml +134 -0
  129. data/xml/x509_subjects.xml +1268 -0
  130. metadata +304 -0
@@ -0,0 +1,94 @@
1
+ module Recog
2
+ class Matcher
3
+ attr_reader :fingerprints, :reporter, :multi_match
4
+
5
+ # @param fingerprints Array of [Recog::Fingerprint] The list of fingerprints from the Recog DB to find possible matches.
6
+ # @param reporter [Recog::MatchReporter] The reporting structure that holds the matches and fails
7
+ # @param multi_match [Boolean] specifies whether or not to use multi-match (true) or not (false)
8
+ def initialize(fingerprints, reporter, multi_match)
9
+ @fingerprints = fingerprints
10
+ @reporter = reporter
11
+ @multi_match = multi_match
12
+ end
13
+
14
+ # @param banner_string [String] A banner string to attempt to match against the Recog DB.
15
+ def match_banner(banner_string)
16
+ all_extractions = []
17
+
18
+ fingerprints.each do |fp|
19
+ extractions = fp.match(banner_string)
20
+ if extractions
21
+ found_extractions = true
22
+ extractions['data'] = banner_string
23
+ if multi_match
24
+ all_extractions << extractions
25
+ else
26
+ all_extractions = [extractions]
27
+ break
28
+ end
29
+ end
30
+ end
31
+
32
+ all_extractions
33
+ end
34
+
35
+ # @param banners_file [String] The source of banners to attempt to match against the Recog DB.
36
+ def match_banners(banners_file)
37
+ reporter.report do
38
+
39
+ fd = $stdin
40
+ file_source = false
41
+
42
+ if banners_file and banners_file != "-"
43
+
44
+ # add fully qualified path if it doesnt start with a '/'
45
+ unless banners_file.first == "/"
46
+ banners_file = "#{File.expand_path("../..", File.dirname(__FILE__))}/#{banners_file}"
47
+ end
48
+
49
+ puts "DEBUG ... loading in recog sigs from #{banners_file}"
50
+
51
+ fd = File.open(banners_file, "rb")
52
+ file_source = true
53
+ end
54
+
55
+ fd.each_line do |line|
56
+ reporter.increment_line_count
57
+
58
+ line = line.to_s.unpack("C*").pack("C*").strip.gsub(/\\[rn]/, '')
59
+ found_extractions = false
60
+
61
+ all_extractions = []
62
+ fingerprints.each do |fp|
63
+ extractions = fp.match(line)
64
+ if extractions
65
+ found_extractions = true
66
+ extractions['data'] = line
67
+ if multi_match
68
+ all_extractions << extractions
69
+ else
70
+ reporter.match "MATCH: #{extractions.inspect}"
71
+ break
72
+ end
73
+ end
74
+ end
75
+
76
+ if found_extractions
77
+ match_prefix = all_extractions.size > 1 ? 'MATCHES' : 'MATCH'
78
+ reporter.match "#{match_prefix}: #{all_extractions.map(&:inspect).join(',')}" if multi_match
79
+ else
80
+ reporter.failure "FAIL: #{line}"
81
+ end
82
+
83
+ if reporter.stop?
84
+ break
85
+ end
86
+
87
+ end
88
+
89
+ fd.close if file_source
90
+
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,14 @@
1
+
2
+ require_relative 'matcher'
3
+ require_relative 'formatter'
4
+ require_relative 'match_reporter'
5
+
6
+ module Recog
7
+ module MatcherFactory
8
+ def self.build(options)
9
+ formatter = Formatter.new(options, $stdout)
10
+ reporter = MatchReporter.new(options, formatter)
11
+ Matcher.new(options.fingerprints, reporter, options.multi_match)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,347 @@
1
+ module Recog
2
+ class Nizer
3
+
4
+ # Default certainty ratings where none are specified in the fingerprint itself
5
+ DEFAULT_OS_CERTAINTY = 0.85 # Most frequent weights are 0.9, 1.0, and 0.5
6
+ DEFAULT_SERVICE_CERTAINTY = 0.85 # Most frequent weight is 0.85
7
+
8
+ # Non-weighted host attributes that can be extracted from fingerprint matches
9
+ HOST_ATTRIBUTES = %W{
10
+ host.domain
11
+ host.id
12
+ host.ip
13
+ host.mac
14
+ host.name
15
+ host.time
16
+ hw.device
17
+ hw.family
18
+ hw.product
19
+ hw.vendor
20
+ }
21
+
22
+ @@db_manager = nil
23
+ @@db_sorted = false
24
+
25
+
26
+ #
27
+ # Load fingerprints from a specific file or directory
28
+ # This will not preserve any fingerprints that have already been loaded
29
+ # @param path [String] Path to file or directory of XML fingerprints
30
+ def self.load_db(path = nil)
31
+ if path
32
+ @@db_manager = Recog::DBManager.new(path)
33
+ else
34
+ @@db_manager = Recog::DBManager.new
35
+ end
36
+
37
+ # Sort the databases, no behavior or result change for those calling
38
+ # Nizer.match or Nizer.multi_match as they have a single DB
39
+ @@db_manager.databases.sort! { |a, b| b.preference <=> a.preference }
40
+ @@db_sorted = true
41
+ end
42
+
43
+ #
44
+ # Destroy the current DBManager object
45
+ def self.unload_db
46
+ @@db_manager = nil
47
+ @@db_sorted = false
48
+ end
49
+
50
+ #
51
+ # Display the fingerprint databases in the order in which they will be used
52
+ # to match banners. This is useful for fingerprint tuning and debugging.
53
+ def self.display_db_order
54
+ self.load_db unless @@db_manager
55
+
56
+ puts format('%s %-22s %-8s %s', 'Preference', 'Database', 'Type', 'Protocol')
57
+ @@db_manager.databases.each do |db|
58
+ puts format('%10.3f %-22s %-8s %s', db.preference, db.match_key,
59
+ db.database_type, db.protocol)
60
+ end
61
+ end
62
+
63
+ #
64
+ # 2016.11 - Rewritten to be wrapper around #match_db_all, functionality
65
+ # and results must remain unchanged.
66
+ #
67
+ # Locate a database that corresponds with the `match_key` and attempt to
68
+ # find a matching {Fingerprint fingerprint}, stopping at the first hit.
69
+ # Returns `nil` when no matching database or fingerprint is found.
70
+ #
71
+ # @param match_key [String] Fingerprint DB name, e.g. 'smb.native_os'
72
+ # @param match_string [String] String to match
73
+ # @return (see Fingerprint#match) or nil
74
+ def self.match(match_key, match_string)
75
+ filter = { match_key: match_key, multi_match: false }
76
+ matches = self.match_all_db(match_string, filter)
77
+
78
+ matches[0]
79
+ end
80
+
81
+ #
82
+ # @param match_key [String] Fingerprint DB name, e.g. 'smb.native_os'
83
+ # @param match_string [String] String to match
84
+ # @return [Array] Array of Fingerprint#match or empty array
85
+ def self.multi_match(match_key, match_string)
86
+ filter = { match_key: match_key, multi_match: true }
87
+ self.match_all_db(match_string, filter)
88
+ end
89
+
90
+ #
91
+ # Search all fingerprint dbs and attempt to find matching
92
+ # {Fingerprint fingerprint}s. It will return the first match found
93
+ # unless the :multi_match option is used to request all matches.
94
+ # Returns an array of all matching fingerprints or an empty array.
95
+ #
96
+ # @param match_string [String] Service banner to match
97
+ # @param [Hash] filters This hash contains filters used to limit the
98
+ # results to just those from specific types of fingerprints.
99
+ # The values that these filters match come from the 'fingerprints' top
100
+ # level element in the fingerprint DB XML or, in the case of 'protocol',
101
+ # this value can be overridden at the individual fingerprint level by
102
+ # setting a value for 'service.protocol'
103
+ #
104
+ # With the exception of 'match_key', the filters below match the
105
+ # 'fingerprints' attributes with the same name.
106
+ # @option filters [String] :match_key Value from XML 'matches' or file name
107
+ # @option filters [String] :database_type fprint db type: service, util.os, etc.
108
+ # @option filters [String] :protocol Protocol (ftp, smtp, etc.)
109
+ # @option filters [Boolean] :multi_match Return all matches instead of first
110
+ # @return [Array] Array of Fingerprint#match or empty array
111
+ def self.match_all_db(match_string, filters = {})
112
+ match_string = match_string.to_s.unpack('C*').pack('C*')
113
+ matches = Array.new # array to hold all fingerprint matches
114
+
115
+ self.load_db unless @@db_manager
116
+
117
+ @@db_manager.databases.each do |db|
118
+ next if filters[:match_key] && !filters[:match_key].eql?(db.match_key)
119
+ next if filters[:database_type] && !filters[:database_type].eql?(db.database_type)
120
+ db.fingerprints.each do |fp|
121
+ m = fp.match(match_string)
122
+ if m
123
+ # Filter on protocol after match since each individual fp
124
+ # can contain its own 'protocol' value that overrides the
125
+ # one set at the DB level.
126
+ matches.push(m) unless filters[:protocol] && !filters[:protocol].eql?(m['service.protocol'])
127
+ return matches unless filters[:multi_match]
128
+ end
129
+ end
130
+ end
131
+
132
+ matches
133
+ end
134
+
135
+ #
136
+ # Consider an array of match outputs, choose the best result, taking into
137
+ # account the granularity of OS vs Version vs SP vs Language. Only consider
138
+ # fields relevant to the host (OS, name, mac address, etc).
139
+ #
140
+ def self.best_os_match(matches)
141
+
142
+ # The result hash we return to the caller
143
+ result = {}
144
+
145
+ # Certain attributes should be evaluated separately
146
+ host_attrs = {}
147
+
148
+ # Bucket matches into matched OS product names
149
+ os_products = {}
150
+
151
+ matches.each do |m|
152
+ # Count how many times each host attribute value is asserted
153
+ (HOST_ATTRIBUTES & m.keys).each do |ha|
154
+ host_attrs[ha] ||= {}
155
+ host_attrs[ha][m[ha]] ||= 0
156
+ host_attrs[ha][m[ha]] += 1
157
+ end
158
+
159
+ next unless m.has_key?('os.product')
160
+
161
+ # Group matches by OS product and normalize certainty
162
+ cm = m.dup
163
+ cm['os.certainty'] = ( m['os.certainty'] || DEFAULT_OS_CERTAINTY ).to_f
164
+ os_products[ cm['os.product'] ] ||= []
165
+ os_products[ cm['os.product'] ] << cm
166
+ end
167
+
168
+ #
169
+ # Select the best host attribute value by highest frequency
170
+ #
171
+ host_attrs.keys.each do |hk|
172
+ ranked_attr = host_attrs[hk].keys.sort do |a,b|
173
+ host_attrs[hk][b] <=> host_attrs[hk][a]
174
+ end
175
+ result[hk] = ranked_attr.first
176
+ end
177
+
178
+ # Unable to guess the OS without OS matches
179
+ unless os_products.keys.length > 0
180
+ return result
181
+ end
182
+
183
+ #
184
+ # Select the best operating system name by combined certainty of all
185
+ # matches within an os.product group. Multiple weak matches can
186
+ # outweigh a single strong match by design.
187
+ #
188
+ ranked_os = os_products.keys.sort do |a,b|
189
+ os_products[b].map{ |r| r['os.certainty'] }.inject(:+) <=>
190
+ os_products[a].map{ |r| r['os.certainty'] }.inject(:+)
191
+ end
192
+
193
+ # Within the best match group, try to fill in missing attributes
194
+ os_name = ranked_os.first
195
+
196
+ # Find the best match within the winning group
197
+ ranked_os_matches = os_products[os_name].sort do |a,b|
198
+ b['os.certainty'] <=> a['os.certainty']
199
+ end
200
+
201
+ # Fill in missing result values in descending order of best match
202
+ ranked_os_matches.each do |rm|
203
+ rm.each_pair do |k,v|
204
+ result[k] ||= v
205
+ end
206
+ end
207
+
208
+ result
209
+ end
210
+
211
+ #
212
+ # Consider an array of match outputs, choose the best result, taking into
213
+ # account the granularity of service. Only consider fields relevant to the
214
+ # service.
215
+ #
216
+ def self.best_service_match(matches)
217
+
218
+ # The result hash we return to the caller
219
+ result = {}
220
+
221
+ # Bucket matches into matched service product names
222
+ service_products = {}
223
+
224
+ matches.select{ |m| m.has_key?('service.product') }.each do |m|
225
+ # Group matches by product and normalize certainty
226
+ cm = m.dup
227
+ cm['service.certainty'] = ( m['service.certainty'] || DEFAULT_SERVICE_CERTAINTY ).to_f
228
+ service_products[ cm['service.product'] ] ||= []
229
+ service_products[ cm['service.product'] ] << cm
230
+ end
231
+
232
+ # Unable to guess the service without service matches
233
+ unless service_products.keys.length > 0
234
+ return result
235
+ end
236
+
237
+ #
238
+ # Select the best service name by combined certainty of all matches
239
+ # within an service.product group. Multiple weak matches can
240
+ # outweigh a single strong match by design.
241
+ #
242
+ ranked_service = service_products.keys.sort do |a,b|
243
+ service_products[b].map{ |r| r['service.certainty'] }.inject(:+) <=>
244
+ service_products[a].map{ |r| r['service.certainty'] }.inject(:+)
245
+ end
246
+
247
+ # Within the best match group, try to fill in missing attributes
248
+ service_name = ranked_service.first
249
+
250
+ # Find the best match within the winning group
251
+ ranked_service_matches = service_products[service_name].sort do |a,b|
252
+ b['service.certainty'] <=> a['service.certainty']
253
+ end
254
+
255
+ # Fill in missing service values in descending order of best match
256
+ ranked_service_matches.each do |rm|
257
+ rm.keys.select{ |k| k.index('service.') == 0 }.each do |k|
258
+ result[k] ||= rm[k]
259
+ end
260
+ end
261
+
262
+ result
263
+ end
264
+
265
+ end
266
+ end
267
+
268
+ =begin
269
+
270
+ Current key names:
271
+
272
+ apache.info
273
+ apache.variant
274
+ apache.variant.version
275
+ cookie
276
+ host.domain
277
+ host.id
278
+ host.ip
279
+ host.mac
280
+ host.name
281
+ host.time
282
+ hw.device
283
+ hw.family
284
+ hw.product
285
+ hw.vendor
286
+ imail.eval
287
+ jetty.info
288
+ junction.cookie
289
+ junction.name
290
+ linux.kernel.version
291
+ loadbalancer.poolname
292
+ mdaemon.unregistered
293
+ mercur.os.info
294
+ metainfo.version
295
+ metainfo.version.version
296
+ ms.nttp.version
297
+ notes.build.version
298
+ notes.intl
299
+ ntmail.id
300
+ openssh.comment
301
+ openssh.cvepatch
302
+ os.arch
303
+ os.build
304
+ os.certainty
305
+ os.device
306
+ os.edition
307
+ os.family
308
+ os.product
309
+ os.vendor
310
+ os.version
311
+ os.version.version
312
+ os.version.version.version
313
+ postfix.os.info
314
+ postoffice.build
315
+ postoffice.id
316
+ proftpd.server.name
317
+ pureftpd.config
318
+ qpopper.version
319
+ sendmail.config.version
320
+ sendmail.hpux.phne.version
321
+ sendmail.vendor.version
322
+ service.certainty
323
+ service.component.family
324
+ service.component.product
325
+ service.component.vendor
326
+ service.component.version
327
+ service.family
328
+ service.product
329
+ service.vendor
330
+ service.version
331
+ service.version.version
332
+ service.version.version.version
333
+ service.version.version.version.version
334
+ service.version.version.version.version.version
335
+ siemens.model
336
+ snmp.fpmib.oid.1
337
+ snmp.fpmib.oid.2
338
+ system.time
339
+ system.time.format
340
+ system.time.micros
341
+ system.time.millis
342
+ thttpd.mx-patch
343
+ timeout
344
+ tomcat.info
345
+ zmailer.ident
346
+
347
+ =end