recog 2.0.15 → 2.0.16
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/bin/recog_match +5 -1
- data/features/data/multiple_banners_fingerprints.xml +30 -0
- data/features/match.feature +16 -0
- data/lib/recog/matcher.rb +25 -7
- data/lib/recog/matcher_factory.rb +1 -1
- data/lib/recog/nizer.rb +18 -0
- data/lib/recog/version.rb +1 -1
- data/spec/lib/recog/nizer_spec.rb +48 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ebf510b58ac33138c4fdcf5ee549e8a357f705af
|
4
|
+
data.tar.gz: d66456b16899f7af045ab574181bc344ac67a7f6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 86f98b254c38d880fa44c03a2be7d5ba5264163b6b119890fc4a91b40e37b1958e4a201ed84a8b47b5e1ee33a2be52db9920ecfedb243a271f80c6043c978309
|
7
|
+
data.tar.gz: b81423e03118c816b4402c1fc69e42732d5907996889b95d1e28fda0f8cd9dbb945b1fbe4e06a756b0e2bdd54d59d587c44941caa4282a84b88525ab0d8fc5e5
|
data/.gitignore
CHANGED
data/bin/recog_match
CHANGED
@@ -6,7 +6,7 @@ require 'ostruct'
|
|
6
6
|
require 'recog'
|
7
7
|
require 'recog/matcher_factory'
|
8
8
|
|
9
|
-
options = OpenStruct.new(color: false, detail: false, fail_fast: false)
|
9
|
+
options = OpenStruct.new(color: false, detail: false, fail_fast: false, multi_match: false)
|
10
10
|
|
11
11
|
option_parser = OptionParser.new do |opts|
|
12
12
|
opts.banner = "Usage: #{$0} [options] XML_FINGERPRINT_FILE [BANNERS_FILE]"
|
@@ -33,6 +33,10 @@ option_parser = OptionParser.new do |opts|
|
|
33
33
|
options.color = true
|
34
34
|
end
|
35
35
|
|
36
|
+
opts.on("--[no-]multi-match", "Enable or disable multiple matches (defaults to disabled)") do |o|
|
37
|
+
options.multi_match = o
|
38
|
+
end
|
39
|
+
|
36
40
|
opts.on("-h", "--help", "Show this message.") do
|
37
41
|
puts opts
|
38
42
|
exit
|
@@ -0,0 +1,30 @@
|
|
1
|
+
<?xml version="1.0"?>
|
2
|
+
<fingerprints>
|
3
|
+
<fingerprint pattern="FTP">
|
4
|
+
<example>---- FTP Stuff ----</example>
|
5
|
+
<example>FTP server</example>
|
6
|
+
<description>Generic FTP,
|
7
|
+
Checks for the existence of the word FTP in the line
|
8
|
+
</description>
|
9
|
+
<!-- Asserting nothing -->
|
10
|
+
</fingerprint>
|
11
|
+
<fingerprint pattern="^-{10} Welcome to Pure-FTPd (.*)-{10}$">
|
12
|
+
<example>---------- Welcome to Pure-FTPd ----------</example>
|
13
|
+
<description>Pure-FTPd
|
14
|
+
Config data can be zero or more of: [privsep] [TLS]
|
15
|
+
</description>
|
16
|
+
<param pos="1" name="pureftpd.config"/>
|
17
|
+
<param pos="0" name="service.family" value="Pure-FTPd"/>
|
18
|
+
<param pos="0" name="service.product" value="Pure-FTPd"/>
|
19
|
+
</fingerprint>
|
20
|
+
<fingerprint pattern="^(\S+) FTP Server \(SunOS (\S+)\) ready\.?$" flags="REG_ICASE">
|
21
|
+
<description>SunOS/Solaris</description>
|
22
|
+
<example>example.com FTP server (SunOS 5.7) ready.</example>
|
23
|
+
<param pos="0" name="os.vendor" value="Sun"/>
|
24
|
+
<param pos="0" name="os.family" value="Solaris"/>
|
25
|
+
<param pos="0" name="os.product" value="Solaris"/>
|
26
|
+
<param pos="0" name="os.device" value="General"/>
|
27
|
+
<param pos="1" name="host.name"/>
|
28
|
+
<param pos="2" name="os.version"/>
|
29
|
+
</fingerprint>
|
30
|
+
</fingerprints>
|
data/features/match.feature
CHANGED
@@ -14,3 +14,19 @@ Feature: Match
|
|
14
14
|
FAIL: ---------- Welcome to Pure-FTPd [privsep] [TLS] ----------
|
15
15
|
FAIL: polaris FTP server (SunOS 5.8) ready
|
16
16
|
"""
|
17
|
+
|
18
|
+
Scenario: Finds multiple matches
|
19
|
+
When I run `recog_match multiple_banners_fingerprints.xml sample_banner.txt --multi-match`
|
20
|
+
Then it should pass with:
|
21
|
+
"""
|
22
|
+
MATCHES: {"matched"=>"Generic FTP, Checks for the existence of the word FTP in the line", "data"=>"---------- Welcome to Pure-FTPd [privsep] [TLS] ----------"},{"matched"=>"Pure-FTPd Config data can be zero or more of: [privsep] [TLS]", "pureftpd.config"=>"[privsep] [TLS] ", "service.family"=>"Pure-FTPd", "service.product"=>"Pure-FTPd", "data"=>"---------- Welcome to Pure-FTPd [privsep] [TLS] ----------"}
|
23
|
+
MATCHES: {"matched"=>"Generic FTP, Checks for the existence of the word FTP in the line", "data"=>"polaris FTP server (SunOS 5.8) ready."},{"matched"=>"SunOS/Solaris", "os.vendor"=>"Sun", "os.family"=>"Solaris", "os.product"=>"Solaris", "os.device"=>"General", "host.name"=>"polaris", "os.version"=>"5.8", "data"=>"polaris FTP server (SunOS 5.8) ready."}
|
24
|
+
"""
|
25
|
+
|
26
|
+
Scenario: Finds first matches using no-multi-match flag
|
27
|
+
When I run `recog_match multiple_banners_fingerprints.xml sample_banner.txt --no-multi-match`
|
28
|
+
Then it should pass with:
|
29
|
+
"""
|
30
|
+
MATCH: {"matched"=>"Generic FTP, Checks for the existence of the word FTP in the line", "data"=>"---------- Welcome to Pure-FTPd [privsep] [TLS] ----------"}
|
31
|
+
MATCH: {"matched"=>"Generic FTP, Checks for the existence of the word FTP in the line", "data"=>"polaris FTP server (SunOS 5.8) ready."}
|
32
|
+
"""
|
data/lib/recog/matcher.rb
CHANGED
@@ -1,12 +1,17 @@
|
|
1
1
|
module Recog
|
2
2
|
class Matcher
|
3
|
-
attr_reader :fingerprints, :reporter
|
3
|
+
attr_reader :fingerprints, :reporter, :multi_match
|
4
4
|
|
5
|
-
|
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)
|
6
9
|
@fingerprints = fingerprints
|
7
10
|
@reporter = reporter
|
11
|
+
@multi_match = multi_match
|
8
12
|
end
|
9
13
|
|
14
|
+
# @param banners_file [String] The source of banners to attempt to match against the Recog DB.
|
10
15
|
def match_banners(banners_file)
|
11
16
|
reporter.report do
|
12
17
|
|
@@ -22,14 +27,26 @@ class Matcher
|
|
22
27
|
reporter.increment_line_count
|
23
28
|
|
24
29
|
line = line.to_s.unpack("C*").pack("C*").strip.gsub(/\\[rn]/, '')
|
25
|
-
|
30
|
+
found_extractions = false
|
31
|
+
|
32
|
+
all_extractions = []
|
26
33
|
fingerprints.each do |fp|
|
27
|
-
|
34
|
+
extractions = fp.match(line)
|
35
|
+
if extractions
|
36
|
+
found_extractions = true
|
37
|
+
extractions['data'] = line
|
38
|
+
if multi_match
|
39
|
+
all_extractions << extractions
|
40
|
+
else
|
41
|
+
reporter.match "MATCH: #{extractions.inspect}"
|
42
|
+
break
|
43
|
+
end
|
44
|
+
end
|
28
45
|
end
|
29
46
|
|
30
|
-
if
|
31
|
-
|
32
|
-
reporter.match "
|
47
|
+
if found_extractions
|
48
|
+
match_prefix = all_extractions.size > 1 ? 'MATCHES' : 'MATCH'
|
49
|
+
reporter.match "#{match_prefix}: #{all_extractions.map(&:inspect).join(',')}" if multi_match
|
33
50
|
else
|
34
51
|
reporter.failure "FAIL: #{line}"
|
35
52
|
end
|
@@ -37,6 +54,7 @@ class Matcher
|
|
37
54
|
if reporter.stop?
|
38
55
|
break
|
39
56
|
end
|
57
|
+
|
40
58
|
end
|
41
59
|
|
42
60
|
fd.close if file_source
|
@@ -8,7 +8,7 @@ module MatcherFactory
|
|
8
8
|
def self.build(options)
|
9
9
|
formatter = Formatter.new(options, $stdout)
|
10
10
|
reporter = MatchReporter.new(options, formatter)
|
11
|
-
Matcher.new(options.fingerprints, reporter)
|
11
|
+
Matcher.new(options.fingerprints, reporter, options.multi_match)
|
12
12
|
end
|
13
13
|
end
|
14
14
|
end
|
data/lib/recog/nizer.rb
CHANGED
@@ -41,6 +41,24 @@ class Nizer
|
|
41
41
|
nil
|
42
42
|
end
|
43
43
|
|
44
|
+
def self.multi_match(match_key, match_string)
|
45
|
+
match_string = match_string.to_s.unpack("C*").pack("C*")
|
46
|
+
@@db_manager ||= Recog::DBManager.new
|
47
|
+
|
48
|
+
matches = Array.new #array to hold all fingerprint matches
|
49
|
+
|
50
|
+
@@db_manager.databases.each do |db|
|
51
|
+
next unless db.match_key == match_key
|
52
|
+
|
53
|
+
db.fingerprints.each do |fp|
|
54
|
+
m = fp.match(match_string)
|
55
|
+
matches.push(m) if m
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
return matches
|
60
|
+
end
|
61
|
+
|
44
62
|
#
|
45
63
|
# Consider an array of match outputs, choose the best result, taking into
|
46
64
|
# account the granularity of OS vs Version vs SP vs Language. Only consider
|
data/lib/recog/version.rb
CHANGED
@@ -26,6 +26,54 @@ describe Recog::Nizer do
|
|
26
26
|
|
27
27
|
end
|
28
28
|
end
|
29
|
+
|
30
|
+
line = 'non-existent'
|
31
|
+
context "with non-existent match" do
|
32
|
+
let(:match_result) {subject.match('smb.native_os', line) }
|
33
|
+
it "returns a nil" do
|
34
|
+
expect(match_result).to be_nil
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe ".multi_match" do
|
40
|
+
File.readlines(File.expand_path(File.join('spec', 'data', 'smb_native_os.txt'))).each do |line|
|
41
|
+
data = line.strip
|
42
|
+
|
43
|
+
context "with smb_native_os:#{data}" do
|
44
|
+
let(:match_results) {subject.multi_match('smb.native_os', data) }
|
45
|
+
|
46
|
+
it "returns an array" do
|
47
|
+
expect(match_results.class).to eq(::Array)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "returns at least one successful match" do
|
51
|
+
expect(match_results.size).to be > 0
|
52
|
+
end
|
53
|
+
|
54
|
+
it "correctly matches service or os" do
|
55
|
+
match_results do |mr|
|
56
|
+
if data =~ /^Windows/
|
57
|
+
expect(mr['os.product']).to match(/^Windows/)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
line = 'non-existent'
|
66
|
+
context "with non-existent match" do
|
67
|
+
let(:match_results) {subject.multi_match('smb.native_os', line) }
|
68
|
+
|
69
|
+
it "returns an array" do
|
70
|
+
expect(match_results.class).to eq(::Array)
|
71
|
+
end
|
72
|
+
|
73
|
+
it "returns an empty array" do
|
74
|
+
expect(match_results).to be_empty
|
75
|
+
end
|
76
|
+
end
|
29
77
|
end
|
30
78
|
|
31
79
|
describe ".best_os_match" do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: recog
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.
|
4
|
+
version: 2.0.16
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rapid7 Research
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-11-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|
@@ -136,6 +136,7 @@ files:
|
|
136
136
|
- bin/recog_verify
|
137
137
|
- features/data/failing_banners_fingerprints.xml
|
138
138
|
- features/data/matching_banners_fingerprints.xml
|
139
|
+
- features/data/multiple_banners_fingerprints.xml
|
139
140
|
- features/data/no_tests.xml
|
140
141
|
- features/data/sample_banner.txt
|
141
142
|
- features/data/successful_tests.xml
|