new_cms_scanner 0.13.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +19 -0
- data/README.md +26 -0
- data/app/app.rb +24 -0
- data/app/controllers/core/cli_options.rb +117 -0
- data/app/controllers/core.rb +82 -0
- data/app/controllers/interesting_findings.rb +25 -0
- data/app/finders/interesting_findings/fantastico_fileslist.rb +21 -0
- data/app/finders/interesting_findings/headers.rb +17 -0
- data/app/finders/interesting_findings/robots_txt.rb +20 -0
- data/app/finders/interesting_findings/search_replace_db_2.rb +19 -0
- data/app/finders/interesting_findings/xml_rpc.rb +61 -0
- data/app/finders/interesting_findings.rb +25 -0
- data/app/formatters/cli.rb +65 -0
- data/app/formatters/cli_no_color.rb +9 -0
- data/app/formatters/cli_no_colour.rb +17 -0
- data/app/formatters/json.rb +14 -0
- data/app/models/fantastico_fileslist.rb +34 -0
- data/app/models/headers.rb +44 -0
- data/app/models/interesting_finding.rb +48 -0
- data/app/models/robots_txt.rb +31 -0
- data/app/models/search_replace_db_2.rb +17 -0
- data/app/models/user.rb +35 -0
- data/app/models/version.rb +49 -0
- data/app/models/xml_rpc.rb +78 -0
- data/app/user_agents.txt +46 -0
- data/app/views/cli/core/banner.erb +1 -0
- data/app/views/cli/core/finished.erb +8 -0
- data/app/views/cli/core/help.erb +4 -0
- data/app/views/cli/core/started.erb +6 -0
- data/app/views/cli/core/version.erb +1 -0
- data/app/views/cli/interesting_findings/_array.erb +10 -0
- data/app/views/cli/interesting_findings/findings.erb +23 -0
- data/app/views/cli/scan_aborted.erb +5 -0
- data/app/views/cli/usage.erb +3 -0
- data/app/views/json/core/banner.erb +1 -0
- data/app/views/json/core/finished.erb +10 -0
- data/app/views/json/core/help.erb +4 -0
- data/app/views/json/core/started.erb +5 -0
- data/app/views/json/core/version.erb +1 -0
- data/app/views/json/interesting_findings/findings.erb +24 -0
- data/app/views/json/scan_aborted.erb +5 -0
- data/lib/cms_scanner/browser/actions.rb +48 -0
- data/lib/cms_scanner/browser/options.rb +90 -0
- data/lib/cms_scanner/browser.rb +96 -0
- data/lib/cms_scanner/cache/file_store.rb +77 -0
- data/lib/cms_scanner/cache/typhoeus.rb +25 -0
- data/lib/cms_scanner/controller.rb +105 -0
- data/lib/cms_scanner/controllers.rb +67 -0
- data/lib/cms_scanner/errors/http.rb +72 -0
- data/lib/cms_scanner/errors/scan.rb +14 -0
- data/lib/cms_scanner/errors.rb +11 -0
- data/lib/cms_scanner/exit_code.rb +25 -0
- data/lib/cms_scanner/finders/base_finders.rb +45 -0
- data/lib/cms_scanner/finders/finder/breadth_first_dictionary_attack.rb +121 -0
- data/lib/cms_scanner/finders/finder/enumerator.rb +77 -0
- data/lib/cms_scanner/finders/finder/fingerprinter.rb +48 -0
- data/lib/cms_scanner/finders/finder/smart_url_checker/findings.rb +33 -0
- data/lib/cms_scanner/finders/finder/smart_url_checker.rb +60 -0
- data/lib/cms_scanner/finders/finder.rb +75 -0
- data/lib/cms_scanner/finders/finding.rb +54 -0
- data/lib/cms_scanner/finders/findings.rb +26 -0
- data/lib/cms_scanner/finders/independent_finder.rb +30 -0
- data/lib/cms_scanner/finders/independent_finders.rb +26 -0
- data/lib/cms_scanner/finders/same_type_finder.rb +19 -0
- data/lib/cms_scanner/finders/same_type_finders.rb +26 -0
- data/lib/cms_scanner/finders/unique_finder.rb +19 -0
- data/lib/cms_scanner/finders/unique_finders.rb +47 -0
- data/lib/cms_scanner/finders.rb +12 -0
- data/lib/cms_scanner/formatter/buffer.rb +17 -0
- data/lib/cms_scanner/formatter.rb +149 -0
- data/lib/cms_scanner/helper.rb +7 -0
- data/lib/cms_scanner/numeric.rb +13 -0
- data/lib/cms_scanner/parsed_cli.rb +37 -0
- data/lib/cms_scanner/progressbar_null_output.rb +23 -0
- data/lib/cms_scanner/public_suffix/domain.rb +42 -0
- data/lib/cms_scanner/references.rb +132 -0
- data/lib/cms_scanner/scan.rb +88 -0
- data/lib/cms_scanner/target/hashes.rb +45 -0
- data/lib/cms_scanner/target/platform/php.rb +62 -0
- data/lib/cms_scanner/target/platform.rb +3 -0
- data/lib/cms_scanner/target/scope.rb +103 -0
- data/lib/cms_scanner/target/server/apache.rb +27 -0
- data/lib/cms_scanner/target/server/generic.rb +72 -0
- data/lib/cms_scanner/target/server/iis.rb +29 -0
- data/lib/cms_scanner/target/server/nginx.rb +27 -0
- data/lib/cms_scanner/target/server.rb +6 -0
- data/lib/cms_scanner/target.rb +124 -0
- data/lib/cms_scanner/typhoeus/hydra.rb +12 -0
- data/lib/cms_scanner/typhoeus/response.rb +27 -0
- data/lib/cms_scanner/version.rb +6 -0
- data/lib/cms_scanner/vulnerability.rb +46 -0
- data/lib/cms_scanner/web_site.rb +145 -0
- data/lib/cms_scanner.rb +141 -0
- metadata +426 -0
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMSScanner
|
4
|
+
module Finders
|
5
|
+
class Finder
|
6
|
+
# Module to provide an easy way to enumerate items such as plugins, themes etc
|
7
|
+
module Enumerator
|
8
|
+
# @return [ Hash ]
|
9
|
+
def head_or_get_request_params
|
10
|
+
# Disabling the cache, as it causes a 'stack level too deep' exception
|
11
|
+
# with a large number of requests.
|
12
|
+
# See https://github.com/typhoeus/typhoeus/issues/408
|
13
|
+
@head_or_get_request_params ||= target.head_or_get_params.merge(cache_ttl: 0)
|
14
|
+
end
|
15
|
+
|
16
|
+
# @return [ Array<Integer> ]
|
17
|
+
def valid_response_codes
|
18
|
+
@valid_response_codes ||= [200]
|
19
|
+
end
|
20
|
+
|
21
|
+
# @param [ Hash ] The target urls
|
22
|
+
# @param [ Hash ] opts
|
23
|
+
# @option opts [ Boolean ] :show_progression Wether or not to display the progress bar
|
24
|
+
# @option opts [ Regexp ] :exclude_content
|
25
|
+
# @option opts [ Boolean, Array, String ] :check_full_response
|
26
|
+
#
|
27
|
+
# @yield [ Typhoeus::Response, String ]
|
28
|
+
def enumerate(urls, opts = {})
|
29
|
+
create_progress_bar(opts.merge(total: urls.size))
|
30
|
+
|
31
|
+
urls.each do |url, id|
|
32
|
+
request = browser.forge_request(url, head_or_get_request_params)
|
33
|
+
|
34
|
+
request.on_complete do |head_res|
|
35
|
+
progress_bar.increment
|
36
|
+
|
37
|
+
next unless valid_response_codes.include?(head_res.code)
|
38
|
+
|
39
|
+
next if opts[:exclude_content] && head_res.response_headers&.match(opts[:exclude_content])
|
40
|
+
|
41
|
+
head_or_full_res = maybe_get_full_response(head_res, opts)
|
42
|
+
|
43
|
+
yield head_or_full_res, id if head_or_full_res
|
44
|
+
end
|
45
|
+
|
46
|
+
hydra.queue(request)
|
47
|
+
end
|
48
|
+
|
49
|
+
hydra.run
|
50
|
+
end
|
51
|
+
|
52
|
+
# @param [ Typhoeus::Response ] head_res
|
53
|
+
# @param [ Hash ] opts
|
54
|
+
#
|
55
|
+
# @return [ Typhoeus::Response, nil ]
|
56
|
+
def maybe_get_full_response(head_res, opts)
|
57
|
+
return head_res unless opts[:check_full_response] == true ||
|
58
|
+
Array(opts[:check_full_response]).include?(head_res.code)
|
59
|
+
|
60
|
+
full_res = NS::Browser.get(head_res.effective_url, full_request_params)
|
61
|
+
|
62
|
+
return unless valid_response_codes.include?(full_res.code)
|
63
|
+
|
64
|
+
return if target.homepage_or_404?(full_res) ||
|
65
|
+
(opts[:exclude_content] && full_res.body&.match(opts[:exclude_content]))
|
66
|
+
|
67
|
+
full_res
|
68
|
+
end
|
69
|
+
|
70
|
+
# @return [ Hash ]
|
71
|
+
def full_request_params
|
72
|
+
@full_request_params ||= {}
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMSScanner
|
4
|
+
module Finders
|
5
|
+
class Finder
|
6
|
+
# Module to provide an easy way to fingerprint things such as versions
|
7
|
+
module Fingerprinter
|
8
|
+
include Enumerator
|
9
|
+
|
10
|
+
# @param [ Hash ] fingerprints The fingerprints
|
11
|
+
# Format should be like the following:
|
12
|
+
# {
|
13
|
+
# file_path_1: {
|
14
|
+
# md5_hash_1: version_1,
|
15
|
+
# md5_hash_2: [version_2]
|
16
|
+
# },
|
17
|
+
# file_path_2: {
|
18
|
+
# md5_hash_3: [version_1, version_2],
|
19
|
+
# md5_hash_4: version_3
|
20
|
+
# }
|
21
|
+
# }
|
22
|
+
# Note that the version can either be an array or a string
|
23
|
+
#
|
24
|
+
# @param [ Hash ] opts
|
25
|
+
# @option opts [ Boolean ] :show_progression Wether or not to display the progress bar
|
26
|
+
#
|
27
|
+
# @yield [ Mixed, String, String ] version/s, url, hash The version associated to the
|
28
|
+
# fingerprint of the url
|
29
|
+
def fingerprint(fingerprints, opts = {})
|
30
|
+
enum_opts = opts.merge(check_full_response: 200)
|
31
|
+
|
32
|
+
enumerate(fingerprints.transform_keys { |k| target.url(k) }, enum_opts) do |res, fingerprint|
|
33
|
+
md5sum = hexdigest(res.body)
|
34
|
+
|
35
|
+
next unless fingerprint.key?(md5sum)
|
36
|
+
|
37
|
+
yield fingerprint[md5sum], res.effective_url, md5sum
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return [ String ] The hashed value for the given body
|
42
|
+
def hexdigest(body)
|
43
|
+
Digest::MD5.hexdigest(body)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMSScanner
|
4
|
+
module Finders
|
5
|
+
class Finder
|
6
|
+
module SmartURLChecker
|
7
|
+
# Findings
|
8
|
+
class Findings < Array
|
9
|
+
def <<(finding)
|
10
|
+
return self unless finding
|
11
|
+
|
12
|
+
each do |f|
|
13
|
+
next unless f == finding && f.found_by == finding.found_by
|
14
|
+
|
15
|
+
# This makes sure entries added are unique
|
16
|
+
# and prevent pages redirecting to the same one to be added twice
|
17
|
+
entries_to_add = finding.interesting_entries - f.interesting_entries
|
18
|
+
return self if entries_to_add.empty?
|
19
|
+
|
20
|
+
entries_to_add.each { |entry| f.interesting_entries << entry }
|
21
|
+
|
22
|
+
f.confidence += finding.confidence
|
23
|
+
|
24
|
+
return self
|
25
|
+
end
|
26
|
+
|
27
|
+
super(finding)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cms_scanner/finders/finder/smart_url_checker/findings'
|
4
|
+
|
5
|
+
module CMSScanner
|
6
|
+
module Finders
|
7
|
+
class Finder
|
8
|
+
# Smart URL Checker
|
9
|
+
# Typically used when some URLs are potentially in the homepage. If they are found
|
10
|
+
# in it, they will be checked in the #passive (like a browser/client would do when loading the page).
|
11
|
+
# Otherwise they will be checked in the #aggressive
|
12
|
+
module SmartURLChecker
|
13
|
+
# @param [ Array<String> ] urls
|
14
|
+
# @param [ Hash ] opts
|
15
|
+
#
|
16
|
+
# @return []
|
17
|
+
def process_urls(_urls, _opts = {})
|
18
|
+
raise NotImplementedError
|
19
|
+
end
|
20
|
+
|
21
|
+
# @param [ Hash ] opts
|
22
|
+
#
|
23
|
+
# @return [ Array<Finding> ]
|
24
|
+
def passive(opts = {})
|
25
|
+
process_urls(passive_urls(opts), opts)
|
26
|
+
end
|
27
|
+
|
28
|
+
# @param [ Hash ] opts
|
29
|
+
#
|
30
|
+
# @return [ Array<String> ]
|
31
|
+
def passive_urls(_opts = {})
|
32
|
+
target.in_scope_uris(target.homepage_res, passive_urls_xpath).map(&:to_s)
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [ String ]
|
36
|
+
def passive_urls_xpath
|
37
|
+
raise NotImplementedError
|
38
|
+
end
|
39
|
+
|
40
|
+
# @param [ Hash ] opts
|
41
|
+
#
|
42
|
+
# @return [ Array<Finding> ]
|
43
|
+
def aggressive(opts = {})
|
44
|
+
# To avoid scanning the same twice
|
45
|
+
urls = aggressive_urls(opts)
|
46
|
+
urls -= passive_urls(opts) if opts[:mode] == :mixed
|
47
|
+
|
48
|
+
process_urls(urls, opts)
|
49
|
+
end
|
50
|
+
|
51
|
+
# @param [ Hash ] opts
|
52
|
+
#
|
53
|
+
# @return [ Array<String> ]
|
54
|
+
def aggressive_urls(_opts = {})
|
55
|
+
raise NotImplementedError
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cms_scanner/finders/finder/smart_url_checker'
|
4
|
+
require 'cms_scanner/finders/finder/enumerator'
|
5
|
+
require 'cms_scanner/finders/finder/fingerprinter'
|
6
|
+
require 'cms_scanner/finders/finder/breadth_first_dictionary_attack'
|
7
|
+
|
8
|
+
module CMSScanner
|
9
|
+
module Finders
|
10
|
+
# Finder
|
11
|
+
class Finder
|
12
|
+
# Constants for common found_by
|
13
|
+
DIRECT_ACCESS = 'Direct Access (Aggressive Detection)'
|
14
|
+
|
15
|
+
attr_accessor :target, :progress_bar
|
16
|
+
|
17
|
+
def initialize(target)
|
18
|
+
@target = target
|
19
|
+
end
|
20
|
+
|
21
|
+
# @return [ String ] The titleized name of the finder
|
22
|
+
def titleize
|
23
|
+
# Put a _ char before any digits except those at the end, which will be replaced by a space
|
24
|
+
# Otherwise, class such as Error404Page are returned as Error404 Page instead of Error 404 page
|
25
|
+
# The keep_id_suffix is to concevert classes such as CssId to Css Id instead of Css
|
26
|
+
|
27
|
+
@titleize ||= self.class.to_s.demodulize.gsub(/(\d+)[a-z]+/i, '_\0').titleize(keep_id_suffix: true)
|
28
|
+
end
|
29
|
+
|
30
|
+
# @param [ Hash ] _opts
|
31
|
+
def passive(_opts = {}); end
|
32
|
+
|
33
|
+
# @param [ Hash ] _opts
|
34
|
+
def aggressive(_opts = {}); end
|
35
|
+
|
36
|
+
# @param [ Hash ] opts See https://github.com/jfelchner/ruby-progressbar/wiki/Options
|
37
|
+
# @option opts [ Boolean ] :show_progression
|
38
|
+
#
|
39
|
+
# @return [ ProgressBar::Base ]
|
40
|
+
def create_progress_bar(opts = {})
|
41
|
+
bar_opts = { format: '%t %a <%B> (%c / %C) %P%% %e' }
|
42
|
+
bar_opts[:output] = ProgressBarNullOutput unless opts[:show_progression]
|
43
|
+
|
44
|
+
@progress_bar = ::ProgressBar.create(bar_opts.merge(opts))
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [ Browser ]
|
48
|
+
def browser
|
49
|
+
@browser ||= NS::Browser.instance
|
50
|
+
end
|
51
|
+
|
52
|
+
# @return [ Typhoeus::Hydra ]
|
53
|
+
def hydra
|
54
|
+
@hydra ||= browser.hydra
|
55
|
+
end
|
56
|
+
|
57
|
+
# @param [String, Class ] klass
|
58
|
+
# @return [ String ]
|
59
|
+
def found_by(klass = self.class)
|
60
|
+
labels = %w[aggressive passive]
|
61
|
+
|
62
|
+
caller_locations.each do |call|
|
63
|
+
label = call.label
|
64
|
+
|
65
|
+
next unless labels.include? label
|
66
|
+
|
67
|
+
title = klass.to_s.demodulize.gsub(/(\d+)[a-z]+/i, '_\0').titleize(keep_id_suffix: true)
|
68
|
+
|
69
|
+
return "#{title} (#{label.capitalize} Detection)"
|
70
|
+
end
|
71
|
+
nil
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMSScanner
|
4
|
+
module Finders
|
5
|
+
# Finding
|
6
|
+
module Finding
|
7
|
+
# Fix for "Double/Dynamic Inclusion Problem"
|
8
|
+
def self.included(base)
|
9
|
+
base.include References
|
10
|
+
super(base)
|
11
|
+
end
|
12
|
+
|
13
|
+
FINDING_OPTS = %i[confidence confirmed_by references found_by interesting_entries].freeze
|
14
|
+
|
15
|
+
attr_accessor(*FINDING_OPTS)
|
16
|
+
|
17
|
+
# @return [ Array ]
|
18
|
+
def confirmed_by
|
19
|
+
@confirmed_by ||= []
|
20
|
+
end
|
21
|
+
|
22
|
+
# Should be overriden in child classes
|
23
|
+
# @return [ Array ]
|
24
|
+
def interesting_entries
|
25
|
+
@interesting_entries ||= []
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return [ Integer ]
|
29
|
+
def confidence
|
30
|
+
@confidence ||= 0
|
31
|
+
end
|
32
|
+
|
33
|
+
# @param [ Integer ] value
|
34
|
+
def confidence=(value)
|
35
|
+
@confidence = value >= 100 ? 100 : value
|
36
|
+
end
|
37
|
+
|
38
|
+
# @param [ Hash ] opts
|
39
|
+
def parse_finding_options(opts = {})
|
40
|
+
FINDING_OPTS.each { |opt| send("#{opt}=", opts[opt]) if opts.key?(opt) }
|
41
|
+
end
|
42
|
+
|
43
|
+
# TODO: maybe also check for interesting_entries and confirmed_by ?
|
44
|
+
# So far this is used in specs only
|
45
|
+
def eql?(other)
|
46
|
+
self == other && confidence == other.confidence && found_by == other.found_by
|
47
|
+
end
|
48
|
+
|
49
|
+
def <=>(other)
|
50
|
+
to_s.downcase <=> other.to_s.downcase
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMSScanner
|
4
|
+
module Finders
|
5
|
+
# Findings container
|
6
|
+
class Findings < Array
|
7
|
+
# Override to include the confirmed_by logic
|
8
|
+
#
|
9
|
+
# @param [ Finding ] finding
|
10
|
+
def <<(finding)
|
11
|
+
return self unless finding
|
12
|
+
|
13
|
+
each do |found|
|
14
|
+
next unless found == finding
|
15
|
+
|
16
|
+
found.confirmed_by << finding
|
17
|
+
found.confidence += finding.confidence
|
18
|
+
|
19
|
+
return self
|
20
|
+
end
|
21
|
+
|
22
|
+
super(finding)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMSScanner
|
4
|
+
module Finders
|
5
|
+
# Independent Finder
|
6
|
+
module IndependentFinder
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
# See ActiveSupport::Concern
|
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
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMSScanner
|
4
|
+
module Finders
|
5
|
+
# This class is designed to handle independent results
|
6
|
+
# which are not related with each others
|
7
|
+
# e.g: interesting files
|
8
|
+
class IndependentFinders < BaseFinders
|
9
|
+
# @param [ Hash ] opts
|
10
|
+
# @option opts [ Symbol ] mode :mixed, :passive or :aggressive
|
11
|
+
#
|
12
|
+
# @return [ Findings ]
|
13
|
+
def run(opts = {})
|
14
|
+
methods = symbols_from_mode(opts[:mode])
|
15
|
+
|
16
|
+
each do |finder|
|
17
|
+
methods.each do |symbol|
|
18
|
+
run_finder(finder, symbol, opts)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
filter_findings
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMSScanner
|
4
|
+
module Finders
|
5
|
+
# Same Type Finder
|
6
|
+
module SameTypeFinder
|
7
|
+
def self.included(klass)
|
8
|
+
klass.class_eval do
|
9
|
+
include IndependentFinder
|
10
|
+
|
11
|
+
# @return [ Array ]
|
12
|
+
def finders
|
13
|
+
@finders ||= NS::Finders::SameTypeFinders.new
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMSScanner
|
4
|
+
module Finders
|
5
|
+
# This class is designed to handle same type results, such as enumeration of plugins,
|
6
|
+
# themes etc.
|
7
|
+
class SameTypeFinders < BaseFinders
|
8
|
+
# @param [ Hash ] opts
|
9
|
+
# @option opts [ Symbol ] :mode :mixed, :passive or :aggressive
|
10
|
+
# @option opts [ Boolean ] :sort Wether or not to sort the findings
|
11
|
+
#
|
12
|
+
# @return [ Findings ]
|
13
|
+
def run(opts = {})
|
14
|
+
symbols_from_mode(opts[:mode]).each do |symbol|
|
15
|
+
each do |finder|
|
16
|
+
run_finder(finder, symbol, opts)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
findings.sort! if opts[:sort]
|
21
|
+
|
22
|
+
filter_findings
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMSScanner
|
4
|
+
module Finders
|
5
|
+
# Unique Finder
|
6
|
+
module UniqueFinder
|
7
|
+
def self.included(klass)
|
8
|
+
klass.class_eval do
|
9
|
+
include IndependentFinder
|
10
|
+
|
11
|
+
# @return [ Array ]
|
12
|
+
def finders
|
13
|
+
@finders ||= NS::Finders::UniqueFinders.new
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMSScanner
|
4
|
+
module Finders
|
5
|
+
# This class is designed to return a unique result such as a version
|
6
|
+
# Note: Finders contained can return multiple results but the #run will only
|
7
|
+
# returned the best finding
|
8
|
+
class UniqueFinders < BaseFinders
|
9
|
+
# @param [ Hash ] opts
|
10
|
+
# @option opts [ Symbol ] :mode :mixed, :passive or :aggressive
|
11
|
+
# @option opts [ Int ] :confidence_threshold If a finding's confidence reaches this value,
|
12
|
+
# it will be returned as the best finding.
|
13
|
+
# Default is 100.
|
14
|
+
# If <= 0, all finders will be ran.
|
15
|
+
#
|
16
|
+
# @return [ Object, false ] The best finding or false if none
|
17
|
+
def run(opts = {})
|
18
|
+
opts[:confidence_threshold] ||= 100
|
19
|
+
|
20
|
+
symbols_from_mode(opts[:mode]).each do |symbol|
|
21
|
+
each do |finder|
|
22
|
+
run_finder(finder, symbol, opts)
|
23
|
+
|
24
|
+
next if opts[:confidence_threshold] <= 0
|
25
|
+
|
26
|
+
findings.each { |f| return f if f.confidence >= opts[:confidence_threshold] }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
filter_findings
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
|
35
|
+
# @return [ Object, false ] The best finding or false if none
|
36
|
+
def filter_findings
|
37
|
+
# results are sorted by confidence ASC
|
38
|
+
findings.sort_by!(&:confidence)
|
39
|
+
|
40
|
+
# If all findings have the same confidence, false is returned
|
41
|
+
return false if findings.size > 1 && findings.first.confidence == findings.last.confidence
|
42
|
+
|
43
|
+
findings.last || false
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cms_scanner/finders/finder'
|
4
|
+
require 'cms_scanner/finders/finding'
|
5
|
+
require 'cms_scanner/finders/findings'
|
6
|
+
require 'cms_scanner/finders/base_finders'
|
7
|
+
require 'cms_scanner/finders/independent_finders'
|
8
|
+
require 'cms_scanner/finders/independent_finder'
|
9
|
+
require 'cms_scanner/finders/unique_finders'
|
10
|
+
require 'cms_scanner/finders/unique_finder'
|
11
|
+
require 'cms_scanner/finders/same_type_finders'
|
12
|
+
require 'cms_scanner/finders/same_type_finder'
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMSScanner
|
4
|
+
module Formatter
|
5
|
+
# Module used to output the rendered views into a buffer
|
6
|
+
# and beautify it a the end of the scan
|
7
|
+
module Buffer
|
8
|
+
def output(tpl, vars = {}, controller_name = nil)
|
9
|
+
buffer << render(tpl, vars, controller_name).encode('UTF-8', invalid: :replace, undef: :replace)
|
10
|
+
end
|
11
|
+
|
12
|
+
def buffer
|
13
|
+
@buffer ||= +''
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|