new_cms_scanner 0.13.7
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 +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
|