cms_scanner 0.0.2
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/.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
|