recog-intrigue 2.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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