cms_scanner 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (147) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +6 -0
  5. data/.travis.yml +14 -0
  6. data/Gemfile +6 -0
  7. data/README.md +20 -0
  8. data/Rakefile +9 -0
  9. data/app/app.rb +4 -0
  10. data/app/controllers.rb +2 -0
  11. data/app/controllers/core.rb +46 -0
  12. data/app/controllers/core/cli_options.rb +68 -0
  13. data/app/controllers/interesting_files.rb +12 -0
  14. data/app/finders.rb +1 -0
  15. data/app/finders/interesting_files.rb +21 -0
  16. data/app/finders/interesting_files/fantastico_fileslist.rb +23 -0
  17. data/app/finders/interesting_files/headers.rb +15 -0
  18. data/app/finders/interesting_files/robots_txt.rb +22 -0
  19. data/app/finders/interesting_files/search_replace_db_2.rb +28 -0
  20. data/app/finders/interesting_files/xml_rpc.rb +62 -0
  21. data/app/formatters.rb +3 -0
  22. data/app/formatters/cli.rb +18 -0
  23. data/app/formatters/cli_no_colour.rb +15 -0
  24. data/app/formatters/json.rb +12 -0
  25. data/app/models.rb +5 -0
  26. data/app/models/fantastico_fileslist.rb +20 -0
  27. data/app/models/headers.rb +37 -0
  28. data/app/models/interesting_file.rb +30 -0
  29. data/app/models/robots_txt.rb +20 -0
  30. data/app/models/xml_rpc.rb +35 -0
  31. data/app/views/cli/core/finished.erb +4 -0
  32. data/app/views/cli/core/started.erb +3 -0
  33. data/app/views/cli/interesting_files/findings.erb +19 -0
  34. data/app/views/cli/scan_aborted.erb +4 -0
  35. data/app/views/json/core/finished.erb +3 -0
  36. data/app/views/json/core/started.erb +3 -0
  37. data/app/views/json/interesting_files/findings.erb +1 -0
  38. data/app/views/json/scan_aborted.erb +4 -0
  39. data/cms_scanner.gemspec +37 -0
  40. data/examples/views/cli/wp_custom/test.erb +1 -0
  41. data/examples/views/json/wp_custom/test.erb +1 -0
  42. data/examples/wpscan.rb +29 -0
  43. data/lib/cms_scanner.rb +71 -0
  44. data/lib/cms_scanner/browser.rb +68 -0
  45. data/lib/cms_scanner/browser/actions.rb +48 -0
  46. data/lib/cms_scanner/browser/options.rb +53 -0
  47. data/lib/cms_scanner/cache/file_store.rb +75 -0
  48. data/lib/cms_scanner/cache/typhoeus.rb +21 -0
  49. data/lib/cms_scanner/controller.rb +90 -0
  50. data/lib/cms_scanner/controllers.rb +34 -0
  51. data/lib/cms_scanner/errors/auth_errors.rb +15 -0
  52. data/lib/cms_scanner/finders.rb +5 -0
  53. data/lib/cms_scanner/finders/finder.rb +27 -0
  54. data/lib/cms_scanner/finders/finding.rb +32 -0
  55. data/lib/cms_scanner/finders/findings.rb +25 -0
  56. data/lib/cms_scanner/finders/independent_finder.rb +30 -0
  57. data/lib/cms_scanner/finders/independent_finders.rb +41 -0
  58. data/lib/cms_scanner/formatter.rb +118 -0
  59. data/lib/cms_scanner/formatter/buffer.rb +15 -0
  60. data/lib/cms_scanner/target.rb +33 -0
  61. data/lib/cms_scanner/target/platform.rb +2 -0
  62. data/lib/cms_scanner/target/platform/php.rb +39 -0
  63. data/lib/cms_scanner/target/platform/wordpress.rb +35 -0
  64. data/lib/cms_scanner/target/platform/wordpress/custom_directories.rb +62 -0
  65. data/lib/cms_scanner/target/server.rb +3 -0
  66. data/lib/cms_scanner/target/server/apache.rb +43 -0
  67. data/lib/cms_scanner/target/server/generic.rb +34 -0
  68. data/lib/cms_scanner/target/server/iis.rb +48 -0
  69. data/lib/cms_scanner/version.rb +4 -0
  70. data/lib/cms_scanner/web_site.rb +68 -0
  71. data/lib/helper.rb +24 -0
  72. data/spec/app/controllers/core_spec.rb +152 -0
  73. data/spec/app/controllers/interesting_files_spec.rb +50 -0
  74. data/spec/app/finders/interesting_files/fantastico_fileslist_spec.rb +68 -0
  75. data/spec/app/finders/interesting_files/headers_spec.rb +38 -0
  76. data/spec/app/finders/interesting_files/robots_txt_spec.rb +56 -0
  77. data/spec/app/finders/interesting_files/search_replace_db_2_spec.rb +55 -0
  78. data/spec/app/finders/interesting_files/xml_rpc_spec.rb +138 -0
  79. data/spec/app/finders/interesting_files_spec.rb +13 -0
  80. data/spec/app/formatters/cli_no_colour_spec.rb +17 -0
  81. data/spec/app/formatters/cli_spec.rb +21 -0
  82. data/spec/app/formatters/json_spec.rb +33 -0
  83. data/spec/app/models/fantastico_fileslist_spec.rb +32 -0
  84. data/spec/app/models/headers_spec.rb +52 -0
  85. data/spec/app/models/interesting_file_spec.rb +51 -0
  86. data/spec/app/models/robots_txt_spec.rb +28 -0
  87. data/spec/app/models/xml_rpc_spec.rb +47 -0
  88. data/spec/cache/.gitignore +4 -0
  89. data/spec/dummy_finders.rb +41 -0
  90. data/spec/fixtures/interesting_files/fantastico_fileslist/fantastico_fileslist.txt +12 -0
  91. data/spec/fixtures/interesting_files/file.txt +4 -0
  92. data/spec/fixtures/interesting_files/headers/interesting.txt +14 -0
  93. data/spec/fixtures/interesting_files/headers/no_interesting.txt +12 -0
  94. data/spec/fixtures/interesting_files/robots_txt/robots.txt +10 -0
  95. data/spec/fixtures/interesting_files/search_replace_db_2/searchreplacedb2.php +188 -0
  96. data/spec/fixtures/interesting_files/xml_rpc/homepage_in_scope_pingback.html +7 -0
  97. data/spec/fixtures/interesting_files/xml_rpc/homepage_out_of_scope_pingback.html +7 -0
  98. data/spec/fixtures/interesting_files/xml_rpc/xmlrpc.php +1 -0
  99. data/spec/fixtures/output.txt +0 -0
  100. data/spec/fixtures/target/platform/php/debug_log/debug.log +2 -0
  101. data/spec/fixtures/target/platform/php/fpd/wp_rss_functions.php +2 -0
  102. data/spec/fixtures/target/platform/wordpress/custom_directories/custom_w_spaces.html +10 -0
  103. data/spec/fixtures/target/platform/wordpress/custom_directories/default.html +14 -0
  104. data/spec/fixtures/target/platform/wordpress/custom_directories/https.html +12 -0
  105. data/spec/fixtures/target/platform/wordpress/detection/default.html +4 -0
  106. data/spec/fixtures/target/platform/wordpress/detection/not_wp.html +8 -0
  107. data/spec/fixtures/target/platform/wordpress/detection/wp_includes.html +3 -0
  108. data/spec/fixtures/target/server/apache/directory_listing/2.2.16.html +15 -0
  109. data/spec/fixtures/target/server/generic/server/apache/basic.txt +5 -0
  110. data/spec/fixtures/target/server/generic/server/iis/basic.txt +6 -0
  111. data/spec/fixtures/target/server/generic/server/not_detected.txt +3 -0
  112. data/spec/fixtures/target/server/iis/directory_listing/no_parent.html +3 -0
  113. data/spec/fixtures/target/server/iis/directory_listing/with_parent.html +3 -0
  114. data/spec/fixtures/views/base/ctrl/local.erb +1 -0
  115. data/spec/fixtures/views/base/ctrl/test.erb +3 -0
  116. data/spec/fixtures/views/base/global.erb +1 -0
  117. data/spec/fixtures/views/base/test.erb +2 -0
  118. data/spec/fixtures/views/based_format/test.erb +1 -0
  119. data/spec/fixtures/views/json/render_me.erb +4 -0
  120. data/spec/lib/browser_spec.rb +141 -0
  121. data/spec/lib/cache/file_store_spec.rb +101 -0
  122. data/spec/lib/cache/typhoeus_spec.rb +30 -0
  123. data/spec/lib/cms_scanner_spec.rb +45 -0
  124. data/spec/lib/controller_spec.rb +23 -0
  125. data/spec/lib/controllers_spec.rb +52 -0
  126. data/spec/lib/finders/findings_spec.rb +49 -0
  127. data/spec/lib/finders/independent_finders_spec.rb +98 -0
  128. data/spec/lib/formatter_spec.rb +136 -0
  129. data/spec/lib/sub_scanner_spec.rb +27 -0
  130. data/spec/lib/target/platforms_spec.rb +13 -0
  131. data/spec/lib/target/servers_spec.rb +13 -0
  132. data/spec/lib/target_spec.rb +50 -0
  133. data/spec/lib/web_site_spec.rb +124 -0
  134. data/spec/shared_examples.rb +11 -0
  135. data/spec/shared_examples/browser_actions.rb +32 -0
  136. data/spec/shared_examples/finding.rb +20 -0
  137. data/spec/shared_examples/formatter_buffer.rb +8 -0
  138. data/spec/shared_examples/formatter_class_methods.rb +26 -0
  139. data/spec/shared_examples/independent_finder.rb +33 -0
  140. data/spec/shared_examples/target/platform/php.rb +58 -0
  141. data/spec/shared_examples/target/platform/wordpress.rb +41 -0
  142. data/spec/shared_examples/target/platform/wordpress/custom_directories.rb +50 -0
  143. data/spec/shared_examples/target/server/apache.rb +33 -0
  144. data/spec/shared_examples/target/server/generic.rb +34 -0
  145. data/spec/shared_examples/target/server/iis.rb +38 -0
  146. data/spec/spec_helper.rb +41 -0
  147. 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,5 @@
1
+ require 'cms_scanner/finders/finder'
2
+ require 'cms_scanner/finders/finding'
3
+ require 'cms_scanner/finders/findings'
4
+ require 'cms_scanner/finders/independent_finders'
5
+ require 'cms_scanner/finders/independent_finder'
@@ -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