cms_scanner 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/.rubocop.yml +6 -0
- data/.travis.yml +14 -0
- data/Gemfile +6 -0
- data/README.md +20 -0
- data/Rakefile +9 -0
- data/app/app.rb +4 -0
- data/app/controllers.rb +2 -0
- data/app/controllers/core.rb +46 -0
- data/app/controllers/core/cli_options.rb +68 -0
- data/app/controllers/interesting_files.rb +12 -0
- data/app/finders.rb +1 -0
- data/app/finders/interesting_files.rb +21 -0
- data/app/finders/interesting_files/fantastico_fileslist.rb +23 -0
- data/app/finders/interesting_files/headers.rb +15 -0
- data/app/finders/interesting_files/robots_txt.rb +22 -0
- data/app/finders/interesting_files/search_replace_db_2.rb +28 -0
- data/app/finders/interesting_files/xml_rpc.rb +62 -0
- data/app/formatters.rb +3 -0
- data/app/formatters/cli.rb +18 -0
- data/app/formatters/cli_no_colour.rb +15 -0
- data/app/formatters/json.rb +12 -0
- data/app/models.rb +5 -0
- data/app/models/fantastico_fileslist.rb +20 -0
- data/app/models/headers.rb +37 -0
- data/app/models/interesting_file.rb +30 -0
- data/app/models/robots_txt.rb +20 -0
- data/app/models/xml_rpc.rb +35 -0
- data/app/views/cli/core/finished.erb +4 -0
- data/app/views/cli/core/started.erb +3 -0
- data/app/views/cli/interesting_files/findings.erb +19 -0
- data/app/views/cli/scan_aborted.erb +4 -0
- data/app/views/json/core/finished.erb +3 -0
- data/app/views/json/core/started.erb +3 -0
- data/app/views/json/interesting_files/findings.erb +1 -0
- data/app/views/json/scan_aborted.erb +4 -0
- data/cms_scanner.gemspec +37 -0
- data/examples/views/cli/wp_custom/test.erb +1 -0
- data/examples/views/json/wp_custom/test.erb +1 -0
- data/examples/wpscan.rb +29 -0
- data/lib/cms_scanner.rb +71 -0
- data/lib/cms_scanner/browser.rb +68 -0
- data/lib/cms_scanner/browser/actions.rb +48 -0
- data/lib/cms_scanner/browser/options.rb +53 -0
- data/lib/cms_scanner/cache/file_store.rb +75 -0
- data/lib/cms_scanner/cache/typhoeus.rb +21 -0
- data/lib/cms_scanner/controller.rb +90 -0
- data/lib/cms_scanner/controllers.rb +34 -0
- data/lib/cms_scanner/errors/auth_errors.rb +15 -0
- data/lib/cms_scanner/finders.rb +5 -0
- data/lib/cms_scanner/finders/finder.rb +27 -0
- data/lib/cms_scanner/finders/finding.rb +32 -0
- data/lib/cms_scanner/finders/findings.rb +25 -0
- data/lib/cms_scanner/finders/independent_finder.rb +30 -0
- data/lib/cms_scanner/finders/independent_finders.rb +41 -0
- data/lib/cms_scanner/formatter.rb +118 -0
- data/lib/cms_scanner/formatter/buffer.rb +15 -0
- data/lib/cms_scanner/target.rb +33 -0
- data/lib/cms_scanner/target/platform.rb +2 -0
- data/lib/cms_scanner/target/platform/php.rb +39 -0
- data/lib/cms_scanner/target/platform/wordpress.rb +35 -0
- data/lib/cms_scanner/target/platform/wordpress/custom_directories.rb +62 -0
- data/lib/cms_scanner/target/server.rb +3 -0
- data/lib/cms_scanner/target/server/apache.rb +43 -0
- data/lib/cms_scanner/target/server/generic.rb +34 -0
- data/lib/cms_scanner/target/server/iis.rb +48 -0
- data/lib/cms_scanner/version.rb +4 -0
- data/lib/cms_scanner/web_site.rb +68 -0
- data/lib/helper.rb +24 -0
- data/spec/app/controllers/core_spec.rb +152 -0
- data/spec/app/controllers/interesting_files_spec.rb +50 -0
- data/spec/app/finders/interesting_files/fantastico_fileslist_spec.rb +68 -0
- data/spec/app/finders/interesting_files/headers_spec.rb +38 -0
- data/spec/app/finders/interesting_files/robots_txt_spec.rb +56 -0
- data/spec/app/finders/interesting_files/search_replace_db_2_spec.rb +55 -0
- data/spec/app/finders/interesting_files/xml_rpc_spec.rb +138 -0
- data/spec/app/finders/interesting_files_spec.rb +13 -0
- data/spec/app/formatters/cli_no_colour_spec.rb +17 -0
- data/spec/app/formatters/cli_spec.rb +21 -0
- data/spec/app/formatters/json_spec.rb +33 -0
- data/spec/app/models/fantastico_fileslist_spec.rb +32 -0
- data/spec/app/models/headers_spec.rb +52 -0
- data/spec/app/models/interesting_file_spec.rb +51 -0
- data/spec/app/models/robots_txt_spec.rb +28 -0
- data/spec/app/models/xml_rpc_spec.rb +47 -0
- data/spec/cache/.gitignore +4 -0
- data/spec/dummy_finders.rb +41 -0
- data/spec/fixtures/interesting_files/fantastico_fileslist/fantastico_fileslist.txt +12 -0
- data/spec/fixtures/interesting_files/file.txt +4 -0
- data/spec/fixtures/interesting_files/headers/interesting.txt +14 -0
- data/spec/fixtures/interesting_files/headers/no_interesting.txt +12 -0
- data/spec/fixtures/interesting_files/robots_txt/robots.txt +10 -0
- data/spec/fixtures/interesting_files/search_replace_db_2/searchreplacedb2.php +188 -0
- data/spec/fixtures/interesting_files/xml_rpc/homepage_in_scope_pingback.html +7 -0
- data/spec/fixtures/interesting_files/xml_rpc/homepage_out_of_scope_pingback.html +7 -0
- data/spec/fixtures/interesting_files/xml_rpc/xmlrpc.php +1 -0
- data/spec/fixtures/output.txt +0 -0
- data/spec/fixtures/target/platform/php/debug_log/debug.log +2 -0
- data/spec/fixtures/target/platform/php/fpd/wp_rss_functions.php +2 -0
- data/spec/fixtures/target/platform/wordpress/custom_directories/custom_w_spaces.html +10 -0
- data/spec/fixtures/target/platform/wordpress/custom_directories/default.html +14 -0
- data/spec/fixtures/target/platform/wordpress/custom_directories/https.html +12 -0
- data/spec/fixtures/target/platform/wordpress/detection/default.html +4 -0
- data/spec/fixtures/target/platform/wordpress/detection/not_wp.html +8 -0
- data/spec/fixtures/target/platform/wordpress/detection/wp_includes.html +3 -0
- data/spec/fixtures/target/server/apache/directory_listing/2.2.16.html +15 -0
- data/spec/fixtures/target/server/generic/server/apache/basic.txt +5 -0
- data/spec/fixtures/target/server/generic/server/iis/basic.txt +6 -0
- data/spec/fixtures/target/server/generic/server/not_detected.txt +3 -0
- data/spec/fixtures/target/server/iis/directory_listing/no_parent.html +3 -0
- data/spec/fixtures/target/server/iis/directory_listing/with_parent.html +3 -0
- data/spec/fixtures/views/base/ctrl/local.erb +1 -0
- data/spec/fixtures/views/base/ctrl/test.erb +3 -0
- data/spec/fixtures/views/base/global.erb +1 -0
- data/spec/fixtures/views/base/test.erb +2 -0
- data/spec/fixtures/views/based_format/test.erb +1 -0
- data/spec/fixtures/views/json/render_me.erb +4 -0
- data/spec/lib/browser_spec.rb +141 -0
- data/spec/lib/cache/file_store_spec.rb +101 -0
- data/spec/lib/cache/typhoeus_spec.rb +30 -0
- data/spec/lib/cms_scanner_spec.rb +45 -0
- data/spec/lib/controller_spec.rb +23 -0
- data/spec/lib/controllers_spec.rb +52 -0
- data/spec/lib/finders/findings_spec.rb +49 -0
- data/spec/lib/finders/independent_finders_spec.rb +98 -0
- data/spec/lib/formatter_spec.rb +136 -0
- data/spec/lib/sub_scanner_spec.rb +27 -0
- data/spec/lib/target/platforms_spec.rb +13 -0
- data/spec/lib/target/servers_spec.rb +13 -0
- data/spec/lib/target_spec.rb +50 -0
- data/spec/lib/web_site_spec.rb +124 -0
- data/spec/shared_examples.rb +11 -0
- data/spec/shared_examples/browser_actions.rb +32 -0
- data/spec/shared_examples/finding.rb +20 -0
- data/spec/shared_examples/formatter_buffer.rb +8 -0
- data/spec/shared_examples/formatter_class_methods.rb +26 -0
- data/spec/shared_examples/independent_finder.rb +33 -0
- data/spec/shared_examples/target/platform/php.rb +58 -0
- data/spec/shared_examples/target/platform/wordpress.rb +41 -0
- data/spec/shared_examples/target/platform/wordpress/custom_directories.rb +50 -0
- data/spec/shared_examples/target/server/apache.rb +33 -0
- data/spec/shared_examples/target/server/generic.rb +34 -0
- data/spec/shared_examples/target/server/iis.rb +38 -0
- data/spec/spec_helper.rb +41 -0
- metadata +432 -0
@@ -0,0 +1,48 @@
|
|
1
|
+
module CMSScanner
|
2
|
+
class Browser
|
3
|
+
# Browser Actions (get, post etc)
|
4
|
+
module Actions
|
5
|
+
# @param [ String ] url
|
6
|
+
# @param [ Hash ] params
|
7
|
+
#
|
8
|
+
# @return [ Typhoeus::Response ]
|
9
|
+
def get(url, params = {})
|
10
|
+
process(url, params.merge(method: :get))
|
11
|
+
end
|
12
|
+
|
13
|
+
# @param [ String ] url
|
14
|
+
# @param [ Hash ] params
|
15
|
+
#
|
16
|
+
# @return [ Typhoeus::Response ]
|
17
|
+
def post(url, params = {})
|
18
|
+
process(url, params.merge(method: :post))
|
19
|
+
end
|
20
|
+
|
21
|
+
# @param [ String ] url
|
22
|
+
# @param [ Hash ] params
|
23
|
+
#
|
24
|
+
# @return [ Typhoeus::Response ]
|
25
|
+
def head(url, params = {})
|
26
|
+
process(url, params.merge(method: :head))
|
27
|
+
end
|
28
|
+
|
29
|
+
# @param [ String ] url
|
30
|
+
# @param [ Hash ] params
|
31
|
+
#
|
32
|
+
# @return [ Typhoeus::Response ]
|
33
|
+
def get_and_follow_location(url, params = {})
|
34
|
+
get(url, params.merge(followlocation: true))
|
35
|
+
end
|
36
|
+
|
37
|
+
protected
|
38
|
+
|
39
|
+
# @param [ String ] url
|
40
|
+
# @param [ Hash ] params
|
41
|
+
#
|
42
|
+
# @return [ Typhoeus::Response ]
|
43
|
+
def process(url, params)
|
44
|
+
Typhoeus::Request.new(url, NS::Browser.instance.request_params(params)).run
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Typhoeus
|
2
|
+
# Hack to have a setter for the :max_concurrency
|
3
|
+
# Which will be officially added in the next version
|
4
|
+
# See: https://github.com/typhoeus/typhoeus/issues/366
|
5
|
+
class Hydra
|
6
|
+
attr_accessor :max_concurrency
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module CMSScanner
|
11
|
+
# Options available in the Browser
|
12
|
+
class Browser
|
13
|
+
OPTIONS = [
|
14
|
+
:cache_ttl,
|
15
|
+
:cookie_jar,
|
16
|
+
:cookie_string,
|
17
|
+
:connect_timeout,
|
18
|
+
:http_auth,
|
19
|
+
:max_threads,
|
20
|
+
:proxy,
|
21
|
+
:proxy_auth,
|
22
|
+
:request_timeout,
|
23
|
+
:user_agent
|
24
|
+
]
|
25
|
+
|
26
|
+
attr_accessor(*OPTIONS)
|
27
|
+
|
28
|
+
# @param [ Hash ] options
|
29
|
+
def load_options(options = {})
|
30
|
+
OPTIONS.each do |sym|
|
31
|
+
send("#{sym}=", options[sym]) if options.key?(sym)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def hydra
|
36
|
+
@hydra ||= Typhoeus::Hydra.new(max_concurrency: max_threads || 1)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Set the threads attribute and update
|
40
|
+
# the max_concurrency of Typhoeus::Hydra
|
41
|
+
#
|
42
|
+
# @param [ Integer ] number
|
43
|
+
def max_threads=(number)
|
44
|
+
@max_threads = number.to_i > 0 ? number.to_i : 1
|
45
|
+
hydra.max_concurrency = @max_threads
|
46
|
+
end
|
47
|
+
|
48
|
+
# Default user agent
|
49
|
+
def user_agent
|
50
|
+
@user_agent ||= "CMSScanner v#{VERSION}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module CMSScanner
|
2
|
+
module Cache
|
3
|
+
# Cache Implementation using files
|
4
|
+
class FileStore
|
5
|
+
attr_reader :storage_path, :serializer
|
6
|
+
|
7
|
+
# The serializer must have the 2 methods #load and #dump
|
8
|
+
# (Marshal and YAML have them)
|
9
|
+
# YAML is Human Readable, contrary to Marshal which store in a binary format
|
10
|
+
# Marshal does not need any "require"
|
11
|
+
#
|
12
|
+
# @param [ String ] storage_path
|
13
|
+
# @param [ ] serializer
|
14
|
+
def initialize(storage_path, serializer = Marshal)
|
15
|
+
@storage_path = File.expand_path(storage_path)
|
16
|
+
@serializer = serializer
|
17
|
+
|
18
|
+
FileUtils.mkdir_p(@storage_path) unless Dir.exist?(@storage_path)
|
19
|
+
end
|
20
|
+
|
21
|
+
# TODO: rename this to clear ?
|
22
|
+
def clean
|
23
|
+
Dir[File.join(storage_path, '*')].each do |f|
|
24
|
+
File.delete(f) unless File.symlink?(f)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# @param [ String ] key
|
29
|
+
#
|
30
|
+
# @return [ Mixed ]
|
31
|
+
def read_entry(key)
|
32
|
+
file_path = entry_path(key)
|
33
|
+
|
34
|
+
return if expired_entry?(key)
|
35
|
+
|
36
|
+
serializer.load(File.read(file_path))
|
37
|
+
rescue
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
|
41
|
+
# @param [ String ] key
|
42
|
+
# @param [ Mixed ] data_to_store
|
43
|
+
# @param [ Integer ] cache_ttl
|
44
|
+
def write_entry(key, data_to_store, cache_ttl)
|
45
|
+
return unless cache_ttl.to_i > 0
|
46
|
+
|
47
|
+
File.write(entry_path(key), serializer.dump(data_to_store))
|
48
|
+
File.write(entry_expiration_path(key), Time.now.to_i + cache_ttl)
|
49
|
+
end
|
50
|
+
|
51
|
+
# @param [ String ] key
|
52
|
+
#
|
53
|
+
# @return [ String ] The file path associated to the key
|
54
|
+
def entry_path(key)
|
55
|
+
File.join(storage_path, key)
|
56
|
+
end
|
57
|
+
|
58
|
+
# @param [ String ] key
|
59
|
+
#
|
60
|
+
# @return [ String ] The expiration file path associated to the key
|
61
|
+
def entry_expiration_path(key)
|
62
|
+
entry_path(key) + '.expiration'
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
# @param [ String ] key
|
68
|
+
#
|
69
|
+
# @return [ Boolean ]
|
70
|
+
def expired_entry?(key)
|
71
|
+
File.read(entry_expiration_path(key)).to_i <= Time.now.to_i rescue true
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'cms_scanner/cache/file_store'
|
2
|
+
|
3
|
+
module CMSScanner
|
4
|
+
module Cache
|
5
|
+
# Cache implementation for Typhoeus
|
6
|
+
class Typhoeus < FileStore
|
7
|
+
# @param [ Typhoeus::Request ] request
|
8
|
+
#
|
9
|
+
# @return [ Typhoeus::Response ]
|
10
|
+
def get(request)
|
11
|
+
read_entry(request.hash.to_s)
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param [ Typhoeus::Request ] request
|
15
|
+
# @param [ Typhoeus::Response ] response
|
16
|
+
def set(request, response)
|
17
|
+
write_entry(request.hash.to_s, response, request.cache_ttl)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module CMSScanner
|
2
|
+
module Controller
|
3
|
+
# Base Controller
|
4
|
+
class Base
|
5
|
+
include OptParseValidator
|
6
|
+
|
7
|
+
# @return [ Array<OptParseValidator::OptBase> ]
|
8
|
+
def cli_options; end
|
9
|
+
|
10
|
+
def before_scan; end
|
11
|
+
|
12
|
+
def run; end
|
13
|
+
|
14
|
+
def after_scan; end
|
15
|
+
|
16
|
+
def ==(other)
|
17
|
+
self.class == other.class
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [ Target ]
|
21
|
+
def target
|
22
|
+
@@target ||= NS::Target.new(parsed_options[:url])
|
23
|
+
end
|
24
|
+
|
25
|
+
# Set the parsed options and initialize the browser
|
26
|
+
# with them
|
27
|
+
#
|
28
|
+
# @param [ Hash ] options
|
29
|
+
def self.parsed_options=(options)
|
30
|
+
@@parsed_options = options
|
31
|
+
|
32
|
+
NS::Browser.instance(options)
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [ Hash ]
|
36
|
+
def parsed_options
|
37
|
+
@@parsed_options ||= {}
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return [ Hash ]
|
41
|
+
def datastore
|
42
|
+
@@datastore ||= {}
|
43
|
+
end
|
44
|
+
|
45
|
+
# @return [ Formatter::Base ]
|
46
|
+
def formatter
|
47
|
+
@@formatter ||= NS::Formatter.load(parsed_options[:format], datastore[:views])
|
48
|
+
end
|
49
|
+
|
50
|
+
# @see Formatter#output
|
51
|
+
#
|
52
|
+
# @return [ Void ]
|
53
|
+
def output(tpl, vars = {})
|
54
|
+
formatter.output(*tpl_params(tpl, vars))
|
55
|
+
end
|
56
|
+
|
57
|
+
# @see Formatter#render
|
58
|
+
#
|
59
|
+
# @return [ String ]
|
60
|
+
def render(tpl, vars = {})
|
61
|
+
formatter.render(*tpl_params(tpl, vars))
|
62
|
+
end
|
63
|
+
|
64
|
+
protected
|
65
|
+
|
66
|
+
# @param [ String ] tpl
|
67
|
+
# @param [ Hash ] vars
|
68
|
+
#
|
69
|
+
# @return [ Array<String> ]
|
70
|
+
def tpl_params(tpl, vars)
|
71
|
+
[
|
72
|
+
tpl,
|
73
|
+
instance_variable_values.merge(vars),
|
74
|
+
self.class.name.demodulize.underscore
|
75
|
+
]
|
76
|
+
end
|
77
|
+
|
78
|
+
# @return [ Hash ] All the instance variable keys (and their values) and the verbose value
|
79
|
+
def instance_variable_values
|
80
|
+
h = { verbose: parsed_options[:verbose] }
|
81
|
+
instance_variables.each do |a|
|
82
|
+
s = a.to_s
|
83
|
+
n = s[1..s.size]
|
84
|
+
h[n.to_sym] = instance_variable_get(a)
|
85
|
+
end
|
86
|
+
h
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module CMSScanner
|
2
|
+
# Controllers Container
|
3
|
+
class Controllers < Array
|
4
|
+
attr_reader :option_parser
|
5
|
+
|
6
|
+
def initialize(option_parser = OptParseValidator::OptParser.new)
|
7
|
+
@option_parser = option_parser
|
8
|
+
end
|
9
|
+
|
10
|
+
# @param [ Controller::Base ] controller
|
11
|
+
#
|
12
|
+
# @retun [ Controllers ] self
|
13
|
+
def <<(controller)
|
14
|
+
options = controller.cli_options
|
15
|
+
|
16
|
+
unless include?(controller)
|
17
|
+
option_parser.add(*options) if options
|
18
|
+
super(controller)
|
19
|
+
end
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
def run
|
24
|
+
parsed_options = option_parser.results
|
25
|
+
first.class.parsed_options = parsed_options
|
26
|
+
|
27
|
+
redirect_output_to_file(parsed_options[:output]) if parsed_options[:output]
|
28
|
+
|
29
|
+
each(&:before_scan)
|
30
|
+
each(&:run)
|
31
|
+
reverse.each(&:after_scan)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module CMSScanner
|
2
|
+
# HTTP Authentication Required Error
|
3
|
+
class HTTPAuthRequiredError < StandardError
|
4
|
+
def message
|
5
|
+
'HTTP authentication required (or was invalid), please provide it with --http-auth'
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
# Proxy Authentication Required Error
|
10
|
+
class ProxyAuthRequiredError < StandardError
|
11
|
+
def message
|
12
|
+
'Proxy authentication required (or was invalid), please provide it with --proxy-auth'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module CMSScanner
|
2
|
+
module Finders
|
3
|
+
# Finder
|
4
|
+
class Finder
|
5
|
+
# Constants for common found_by
|
6
|
+
DIRECT_FILE_ACCESS = 'Direct File Access (aggressive detection)'
|
7
|
+
|
8
|
+
attr_accessor :target
|
9
|
+
|
10
|
+
def initialize(target)
|
11
|
+
@target = target
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param [ Hash ] _opts
|
15
|
+
def passive(_opts = {})
|
16
|
+
end
|
17
|
+
|
18
|
+
# @param [ Hash ] _opts
|
19
|
+
def aggressive(_opts = {})
|
20
|
+
end
|
21
|
+
|
22
|
+
def found_by
|
23
|
+
"#{self.class.to_s.demodulize} (#{caller_locations(1, 1)[0].label} detection)"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module CMSScanner
|
2
|
+
module Finders
|
3
|
+
# Finding
|
4
|
+
module Finding
|
5
|
+
FINDING_OPTS = [:confidence, :confirmed_by, :references, :found_by, :interesting_entries]
|
6
|
+
|
7
|
+
attr_accessor(*FINDING_OPTS)
|
8
|
+
|
9
|
+
# @return [ Array ]
|
10
|
+
def references
|
11
|
+
@references ||= []
|
12
|
+
end
|
13
|
+
|
14
|
+
# @return [ Array ]
|
15
|
+
def confirmed_by
|
16
|
+
@confirmed_by ||= []
|
17
|
+
end
|
18
|
+
|
19
|
+
# Should be overriden in child classes
|
20
|
+
# @return [ Array ]
|
21
|
+
def interesting_entries
|
22
|
+
@interesting_entries ||= []
|
23
|
+
end
|
24
|
+
|
25
|
+
# @param [ Hash ] opts
|
26
|
+
# TODO: Maybe use instance_variable_set ?
|
27
|
+
def parse_finding_options(opts = {})
|
28
|
+
FINDING_OPTS.each { |opt| send("#{opt}=", opts[opt]) if opts.key?(opt) }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module CMSScanner
|
2
|
+
module Finders
|
3
|
+
# Findings container
|
4
|
+
class Findings < Array
|
5
|
+
# Override to include the confirmed_by logic
|
6
|
+
def <<(other)
|
7
|
+
each do |found|
|
8
|
+
next unless found == other
|
9
|
+
|
10
|
+
found.confirmed_by << other
|
11
|
+
# TODO: Increase confidence (e.g: (found + other) / 1.5 ?)
|
12
|
+
return self
|
13
|
+
end
|
14
|
+
|
15
|
+
super(other)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Append the elements of other into self AND returns self
|
19
|
+
# This is not the default behaviour of Array#+ but it's intended
|
20
|
+
def +(other)
|
21
|
+
other.each { |f| self << f }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module CMSScanner
|
2
|
+
module Finders
|
3
|
+
# Independent Finder
|
4
|
+
module IndependentFinder
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
# Hack to have the #find as a class method
|
10
|
+
module ClassMethods
|
11
|
+
def find(target, opts = {})
|
12
|
+
new(target).find(opts)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# @param [ Hash ] opts
|
17
|
+
# @option opts [ Symbol ] mode (:mixed, :passive, :aggressive)
|
18
|
+
#
|
19
|
+
# @return [ Findings ]
|
20
|
+
def find(opts = {})
|
21
|
+
finders.run(opts)
|
22
|
+
end
|
23
|
+
|
24
|
+
# @return [ Array ]
|
25
|
+
def finders
|
26
|
+
@finders ||= NS::Finders::IndependentFinders.new
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|