recog 3.1.1 → 3.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/Gemfile +6 -0
- data/Rakefile +7 -5
- data/lib/recog/db.rb +67 -68
- data/lib/recog/db_manager.rb +22 -21
- data/lib/recog/fingerprint/regexp_factory.rb +10 -13
- data/lib/recog/fingerprint/test.rb +9 -8
- data/lib/recog/fingerprint.rb +252 -262
- data/lib/recog/fingerprint_parse_error.rb +3 -1
- data/lib/recog/formatter.rb +41 -39
- data/lib/recog/match_reporter.rb +82 -83
- data/lib/recog/matcher.rb +37 -40
- data/lib/recog/matcher_factory.rb +7 -6
- data/lib/recog/nizer.rb +218 -224
- data/lib/recog/verifier.rb +30 -28
- data/lib/recog/verify_reporter.rb +69 -73
- data/lib/recog/version.rb +3 -1
- data/lib/recog.rb +2 -0
- data/recog/bin/recog_match +21 -20
- data/recog/xml/apache_modules.xml +2 -0
- data/recog/xml/dhcp_vendor_class.xml +1 -1
- data/recog/xml/favicons.xml +133 -1
- data/recog/xml/ftp_banners.xml +1 -1
- data/recog/xml/html_title.xml +140 -1
- data/recog/xml/http_cookies.xml +20 -2
- data/recog/xml/http_servers.xml +38 -17
- data/recog/xml/http_wwwauth.xml +17 -4
- data/recog/xml/mdns_device-info_txt.xml +49 -15
- data/recog/xml/sip_banners.xml +0 -2
- data/recog/xml/sip_user_agents.xml +1 -1
- data/recog/xml/snmp_sysdescr.xml +1 -2
- data/recog/xml/ssh_banners.xml +8 -0
- data/recog/xml/telnet_banners.xml +3 -2
- data/recog/xml/tls_jarm.xml +1 -1
- data/recog/xml/x11_banners.xml +1 -0
- data/recog/xml/x509_issuers.xml +1 -1
- data/recog/xml/x509_subjects.xml +0 -1
- data/recog.gemspec +14 -13
- data/spec/lib/recog/db_spec.rb +37 -36
- data/spec/lib/recog/fingerprint/regexp_factory_spec.rb +19 -20
- data/spec/lib/recog/fingerprint_spec.rb +44 -42
- data/spec/lib/recog/formatter_spec.rb +20 -18
- data/spec/lib/recog/match_reporter_spec.rb +35 -30
- data/spec/lib/recog/nizer_spec.rb +85 -101
- data/spec/lib/recog/verify_reporter_spec.rb +45 -44
- data/spec/spec_helper.rb +2 -1
- data.tar.gz.sig +1 -3
- metadata +3 -3
- metadata.gz.sig +0 -0
data/lib/recog/formatter.rb
CHANGED
@@ -1,51 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Recog
|
2
|
-
class Formatter
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
attr_reader :options, :output
|
11
|
-
|
12
|
-
def initialize(options, output)
|
13
|
-
@options = options
|
14
|
-
@output = output || StringIO.new
|
15
|
-
end
|
4
|
+
class Formatter
|
5
|
+
COLORS = {
|
6
|
+
red: 31,
|
7
|
+
yellow: 33,
|
8
|
+
green: 32,
|
9
|
+
white: 15
|
10
|
+
}.freeze
|
16
11
|
|
17
|
-
|
18
|
-
output.puts color(text, :white)
|
19
|
-
end
|
12
|
+
attr_reader :options, :output
|
20
13
|
|
21
|
-
|
22
|
-
|
23
|
-
|
14
|
+
def initialize(options, output)
|
15
|
+
@options = options
|
16
|
+
@output = output || StringIO.new
|
17
|
+
end
|
24
18
|
|
25
|
-
|
26
|
-
|
27
|
-
|
19
|
+
def status_message(text)
|
20
|
+
output.puts color(text, :white)
|
21
|
+
end
|
28
22
|
|
29
|
-
|
30
|
-
|
31
|
-
|
23
|
+
def success_message(text)
|
24
|
+
output.puts color(text, :green)
|
25
|
+
end
|
32
26
|
|
33
|
-
|
27
|
+
def warning_message(text)
|
28
|
+
output.puts color(text, :yellow)
|
29
|
+
end
|
34
30
|
|
35
|
-
|
36
|
-
|
37
|
-
|
31
|
+
def failure_message(text)
|
32
|
+
output.puts color(text, :red)
|
33
|
+
end
|
38
34
|
|
39
|
-
|
40
|
-
color_enabled? ? colorize(text, color_code) : text
|
41
|
-
end
|
35
|
+
private
|
42
36
|
|
43
|
-
|
44
|
-
|
45
|
-
|
37
|
+
def color_enabled?
|
38
|
+
options.color
|
39
|
+
end
|
46
40
|
|
47
|
-
|
48
|
-
|
41
|
+
def color(text, color_code)
|
42
|
+
color_enabled? ? colorize(text, color_code) : text
|
43
|
+
end
|
44
|
+
|
45
|
+
def colorize(text, color_code)
|
46
|
+
"\e[#{color_code_for(color_code)}m#{text}\e[0m"
|
47
|
+
end
|
48
|
+
|
49
|
+
def color_code_for(code)
|
50
|
+
COLORS.fetch(code) { COLORS.fetch(:white) }
|
51
|
+
end
|
49
52
|
end
|
50
53
|
end
|
51
|
-
end
|
data/lib/recog/match_reporter.rb
CHANGED
@@ -1,111 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'json'
|
2
4
|
|
3
5
|
module Recog
|
4
|
-
class MatchReporter
|
5
|
-
|
6
|
-
attr_reader :line_count, :match_count, :fail_count
|
7
|
-
|
8
|
-
def initialize(options, formatter)
|
9
|
-
@options = options
|
10
|
-
@formatter = formatter
|
11
|
-
reset_counts
|
12
|
-
end
|
6
|
+
class MatchReporter
|
7
|
+
attr_reader :formatter, :line_count, :match_count, :fail_count
|
13
8
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
9
|
+
def initialize(options, formatter)
|
10
|
+
@options = options
|
11
|
+
@formatter = formatter
|
12
|
+
reset_counts
|
13
|
+
end
|
19
14
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
15
|
+
def report
|
16
|
+
reset_counts
|
17
|
+
yield self
|
18
|
+
summarize unless @options.quiet
|
19
|
+
end
|
24
20
|
|
25
|
-
|
26
|
-
|
27
|
-
end
|
21
|
+
def stop?
|
22
|
+
return false unless @options.fail_fast
|
28
23
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
24
|
+
@fail_count >= @options.stop_after
|
25
|
+
end
|
26
|
+
|
27
|
+
def increment_line_count
|
28
|
+
@line_count += 1
|
29
|
+
end
|
30
|
+
|
31
|
+
def match(match_data)
|
32
|
+
@match_count += 1
|
33
|
+
if @options.json_format
|
34
|
+
# remove data field from all matches and promote to a top-level field
|
35
|
+
data_field = match_data[0]['data']
|
36
|
+
match_data.each { |h| h.delete('data') }
|
37
|
+
new_object = {
|
38
|
+
'data' => data_field
|
39
|
+
}
|
40
|
+
|
41
|
+
if @options.multi_match
|
42
|
+
new_object['matches'] = match_data
|
43
|
+
else
|
44
|
+
new_object['match'] = match_data[0]
|
45
|
+
end
|
46
|
+
msg = new_object.to_json
|
41
47
|
else
|
42
|
-
|
48
|
+
match_prefix = match_data.size > 1 ? 'MATCHES' : 'MATCH'
|
49
|
+
msg = "#{match_prefix}: #{match_data.map(&:inspect).join(',')}"
|
43
50
|
end
|
44
|
-
msg
|
45
|
-
else
|
46
|
-
match_prefix = match_data.size > 1 ? 'MATCHES' : 'MATCH'
|
47
|
-
msg = "#{match_prefix}: #{match_data.map(&:inspect).join(',')}"
|
51
|
+
formatter.success_message(msg.to_s)
|
48
52
|
end
|
49
|
-
formatter.success_message("#{msg}")
|
50
|
-
end
|
51
53
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
54
|
+
def failure(text)
|
55
|
+
@fail_count += 1
|
56
|
+
if @options.json_format
|
57
|
+
new_object = {
|
58
|
+
'data' => text,
|
59
|
+
'match_failure' => true
|
60
|
+
}
|
61
|
+
if @options.multi_match
|
62
|
+
new_object['matches'] = nil
|
63
|
+
else
|
64
|
+
new_object['match'] = nil
|
65
|
+
end
|
66
|
+
msg = new_object.to_json
|
61
67
|
else
|
62
|
-
|
68
|
+
msg = "FAIL: #{text}"
|
63
69
|
end
|
64
|
-
msg
|
65
|
-
else
|
66
|
-
msg = "FAIL: #{text}"
|
70
|
+
formatter.failure_message(msg.to_s)
|
67
71
|
end
|
68
|
-
formatter.failure_message("#{msg}")
|
69
|
-
end
|
70
72
|
|
71
|
-
|
72
|
-
|
73
|
-
|
73
|
+
def print_summary
|
74
|
+
colorize_summary(summary_line)
|
75
|
+
end
|
74
76
|
|
75
|
-
|
77
|
+
private
|
76
78
|
|
77
|
-
|
78
|
-
|
79
|
-
|
79
|
+
def reset_counts
|
80
|
+
@line_count = @match_count = @fail_count = 0
|
81
|
+
end
|
80
82
|
|
81
|
-
|
82
|
-
|
83
|
-
|
83
|
+
def detail?
|
84
|
+
@options.detail
|
85
|
+
end
|
86
|
+
|
87
|
+
def summarize
|
88
|
+
return unless detail?
|
84
89
|
|
85
|
-
def summarize
|
86
|
-
if detail?
|
87
90
|
print_lines_processed
|
88
91
|
print_summary
|
89
92
|
end
|
90
|
-
end
|
91
93
|
|
92
|
-
|
93
|
-
|
94
|
-
|
94
|
+
def print_lines_processed
|
95
|
+
formatter.status_message("\nProcessed #{line_count} lines")
|
96
|
+
end
|
95
97
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
summary << " and #{fail_count} failures"
|
100
|
-
summary
|
101
|
-
end
|
98
|
+
def summary_line
|
99
|
+
"SUMMARY: #{match_count} matches and #{fail_count} failures"
|
100
|
+
end
|
102
101
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
102
|
+
def colorize_summary(summary)
|
103
|
+
if @fail_count > 0
|
104
|
+
formatter.failure_message(summary)
|
105
|
+
else
|
106
|
+
formatter.success_message(summary)
|
107
|
+
end
|
108
108
|
end
|
109
109
|
end
|
110
110
|
end
|
111
|
-
end
|
data/lib/recog/matcher.rb
CHANGED
@@ -1,60 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Recog
|
2
|
-
class Matcher
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
4
|
+
class Matcher
|
5
|
+
attr_reader :fingerprints, :reporter, :multi_match
|
6
|
+
|
7
|
+
# @param fingerprints Array of [Recog::Fingerprint] The list of fingerprints from the Recog DB to find possible matches.
|
8
|
+
# @param reporter [Recog::MatchReporter] The reporting structure that holds the matches and fails
|
9
|
+
# @param multi_match [Boolean] specifies whether or not to use multi-match (true) or not (false)
|
10
|
+
def initialize(fingerprints, reporter, multi_match)
|
11
|
+
@fingerprints = fingerprints
|
12
|
+
@reporter = reporter
|
13
|
+
@multi_match = multi_match
|
14
|
+
end
|
13
15
|
|
14
|
-
|
15
|
-
|
16
|
-
|
16
|
+
# @param banners_file [String] The source of banners to attempt to match against the Recog DB.
|
17
|
+
def match_banners(banners_file)
|
18
|
+
reporter.report do
|
19
|
+
fd = $stdin
|
20
|
+
file_source = false
|
17
21
|
|
18
|
-
|
19
|
-
|
22
|
+
if banners_file && (banners_file != '-')
|
23
|
+
fd = File.open(banners_file, 'rb')
|
24
|
+
file_source = true
|
25
|
+
end
|
20
26
|
|
21
|
-
|
22
|
-
|
23
|
-
file_source = true
|
24
|
-
end
|
27
|
+
fd.each_line do |line|
|
28
|
+
reporter.increment_line_count
|
25
29
|
|
26
|
-
|
27
|
-
|
30
|
+
line = line.to_s.unpack('C*').pack('C*').strip.gsub(/\\[rn]/, '')
|
31
|
+
found_extractions = false
|
28
32
|
|
29
|
-
|
30
|
-
|
33
|
+
extraction_data = []
|
34
|
+
fingerprints.each do |fp|
|
35
|
+
extractions = fp.match(line)
|
36
|
+
next unless extractions
|
31
37
|
|
32
|
-
extraction_data = []
|
33
|
-
fingerprints.each do |fp|
|
34
|
-
extractions = fp.match(line)
|
35
|
-
if extractions
|
36
38
|
found_extractions = true
|
37
39
|
extractions['data'] = line
|
38
40
|
extraction_data << extractions
|
39
41
|
break unless multi_match
|
40
42
|
end
|
41
|
-
end
|
42
43
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
44
|
+
if found_extractions
|
45
|
+
reporter.match extraction_data
|
46
|
+
else
|
47
|
+
reporter.failure line
|
48
|
+
end
|
48
49
|
|
49
|
-
|
50
|
-
break
|
50
|
+
break if reporter.stop?
|
51
51
|
end
|
52
52
|
|
53
|
+
fd.close if file_source
|
53
54
|
end
|
54
|
-
|
55
|
-
fd.close if file_source
|
56
|
-
|
57
55
|
end
|
58
56
|
end
|
59
57
|
end
|
60
|
-
end
|
@@ -1,14 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
|
2
3
|
require 'recog/matcher'
|
3
4
|
require 'recog/formatter'
|
4
5
|
require 'recog/match_reporter'
|
5
6
|
|
6
7
|
module Recog
|
7
|
-
module MatcherFactory
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
module MatcherFactory
|
9
|
+
def self.build(options)
|
10
|
+
formatter = Formatter.new(options, $stdout)
|
11
|
+
reporter = MatchReporter.new(options, formatter)
|
12
|
+
Matcher.new(options.fingerprints, reporter, options.multi_match)
|
13
|
+
end
|
12
14
|
end
|
13
15
|
end
|
14
|
-
end
|