recog 3.1.1 → 3.1.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|