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.
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,41 @@
1
+ module CMSScanner
2
+ module Finders
3
+ # Independent Finders container
4
+ # This class is designed to handle independent results
5
+ # which are not related with others
6
+ # e.g: interesting files
7
+ class IndependentFinders < Array
8
+ # @return [ Findings ]
9
+ def findings
10
+ @findings ||= NS::Finders::Findings.new
11
+ end
12
+
13
+ # @param [ Hash ] opts
14
+ # @option opts [ Symbol ] mode :mixed, :passive or :aggressive
15
+ #
16
+ # @return [ Findings ]
17
+ def run(opts = {})
18
+ each do |finder|
19
+ symbols_from_mode(opts[:mode]).each do |symbol|
20
+ r = finder.send(symbol, opts)
21
+
22
+ next unless r
23
+
24
+ findings + [*r]
25
+ end
26
+ end
27
+
28
+ findings
29
+ end
30
+
31
+ # @param [ Symbol ] mode :mixed, :passive or :aggressive
32
+ # @return [ Array<Symbol> ] The symbols to call for the mode
33
+ def symbols_from_mode(mode)
34
+ symbols = [:passive, :aggressive]
35
+
36
+ return symbols if mode.nil? || mode == :mixed
37
+ symbols.include?(mode) ? [*mode] : []
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,118 @@
1
+ require 'cms_scanner/formatter/buffer'
2
+
3
+ module CMSScanner
4
+ # Formatter
5
+ module Formatter
6
+ # Module to be able to do Formatter.load() & Formatter.availables
7
+ # and do that as well when the Formatter is included in another module
8
+ module ClassMethods
9
+ # @param [ String ] format
10
+ # @param [ Array<String> ] custom_views
11
+ #
12
+ # @return [ Formatter::Base ]
13
+ def load(format = nil, custom_views = nil)
14
+ format ||= 'cli'
15
+ custom_views ||= []
16
+
17
+ f = const_get(format.gsub(/-/, '_').camelize).new
18
+ custom_views.each { |v| f.views_directories << v }
19
+ f
20
+ end
21
+
22
+ # @return [ Array<String> ] The list of the available formatters (except the Base one)
23
+ # @note: the #load method above should then be used to create the associated formatter
24
+ def availables
25
+ formatters = NS::Formatter.constants.select do |const|
26
+ name = NS::Formatter.const_get(const)
27
+ name.is_a?(Class) && name != NS::Formatter::Base
28
+ end
29
+
30
+ formatters.map { |sym| sym.to_s.underscore.dasherize }
31
+ end
32
+ end
33
+
34
+ extend ClassMethods
35
+
36
+ def self.included(base)
37
+ base.extend(ClassMethods)
38
+ end
39
+
40
+ # Base Formatter
41
+ class Base
42
+ attr_reader :controller_name
43
+
44
+ # @return [ String ] The underscored name of the class
45
+ def format
46
+ self.class.name.demodulize.underscore
47
+ end
48
+
49
+ # @return [ String ] The underscored format to use as a base
50
+ def base_format; end
51
+
52
+ # @return [ Array<String> ]
53
+ def formats
54
+ [format, base_format].compact
55
+ end
56
+
57
+ # This is called after the scan
58
+ # and used in some formatters (e.g JSON)
59
+ # to indent results
60
+ def beautify; end
61
+
62
+ # @see #render
63
+ def output(tpl, vars = {}, controller_name = nil)
64
+ puts render(tpl, vars, controller_name)
65
+ end
66
+
67
+ # @param [ String ] tpl
68
+ # @param [ Hash ] vars
69
+ # @param [ String ] controller_name
70
+ def render(tpl, vars = {}, controller_name = nil)
71
+ template_vars(vars)
72
+ @controller_name = controller_name if controller_name
73
+
74
+ # '-' is used to disable new lines when -%> is used
75
+ # See http://www.ruby-doc.org/stdlib-2.1.1/libdoc/erb/rdoc/ERB.html
76
+ ERB.new(File.read(view_path(tpl)), nil, '-').result(binding)
77
+ end
78
+
79
+ # @param [ Hash ] vars
80
+ #
81
+ # @return [ Void ]
82
+ def template_vars(vars)
83
+ vars.each do |key, value|
84
+ instance_variable_set("@#{key}", value) unless key == :views_directories
85
+ end
86
+ end
87
+
88
+ # @param [ String ] tpl
89
+ #
90
+ # @return [ String ] The path of the view
91
+ def view_path(tpl)
92
+ if tpl[0, 1] == '@' # Global Template
93
+ tpl = tpl.delete('@')
94
+ else
95
+ fail 'The controller_name can not be nil' unless controller_name
96
+ tpl = "#{controller_name}/#{tpl}"
97
+ end
98
+
99
+ fail "Wrong tpl format: '#{tpl}'" unless tpl =~ /\A[\w\/_]+\z/
100
+
101
+ views_directories.reverse.each do |dir|
102
+ formats.each do |format|
103
+ potential_file = File.join(dir, format, "#{tpl}.erb")
104
+
105
+ return potential_file if File.exist?(potential_file)
106
+ end
107
+ end
108
+
109
+ fail "View not found for #{format}/#{tpl}"
110
+ end
111
+
112
+ # @return [ Array<String> ] The directories to look into for views
113
+ def views_directories
114
+ @views_directories ||= [Pathname.new(APP_DIR).join('views').to_s]
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,15 @@
1
+ module CMSScanner
2
+ module Formatter
3
+ # Module used to output the rendered views into a buffer
4
+ # and beautify it a the end of the scan
5
+ module Buffer
6
+ def output(tpl, vars = {}, controller_name = nil)
7
+ buffer << render(tpl, vars, controller_name)
8
+ end
9
+
10
+ def buffer
11
+ @buffer ||= ''
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,33 @@
1
+ require 'cms_scanner/web_site'
2
+ require 'cms_scanner/target/platform'
3
+ require 'cms_scanner/target/server'
4
+
5
+ module CMSScanner
6
+ # Target to Scan
7
+ class Target < WebSite
8
+ include Server::Generic
9
+ # @note Subdomains are considered out of scope (maybe consider them in ?)
10
+ # Also, // are handled by Addressable::URI, but worngly :/
11
+ # e.g: Addressable::URI.parse('//file').host => file
12
+ #
13
+ # @param [ String ] url
14
+ #
15
+ # @return [ Boolean ] true if the url given belongs to the target
16
+ def in_scope?(url)
17
+ return true if url[0, 1] == '/' && url[1, 1] != '/'
18
+
19
+ Addressable::URI.parse(url).host == uri.host
20
+ rescue
21
+ false
22
+ end
23
+
24
+ # TODO: add a force option to re-call the #find rather than return the @interesting_files ?
25
+ #
26
+ # @param [ Hash ] opts
27
+ #
28
+ # @return [ Findings ]
29
+ def interesting_files(opts = {})
30
+ @interesting_files ||= NS::Finders::InterestingFiles.find(self, opts)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,2 @@
1
+ require 'cms_scanner/target/platform/php'
2
+ require 'cms_scanner/target/platform/wordpress'
@@ -0,0 +1,39 @@
1
+ module CMSScanner
2
+ class Target < WebSite
3
+ module Platform
4
+ # Some PHP specific implementation
5
+ module PHP
6
+ DEBUG_LOG_PATTERN = /\[[^\]]+\] PHP (?:Warning|Error|Notice):/
7
+ FPD_PATTERN = /Fatal error:.+? in (.+?) on/
8
+
9
+ # @param [ String ] path
10
+ # @param [ Hash ] params The request params
11
+ #
12
+ # @return [ Boolean ] true if url(path) is a debug log, false otherwise
13
+ def debug_log?(path = nil, params = {})
14
+ res = NS::Browser.get(url(path), params.merge(headers: { 'range' => 'bytes=0-700' }))
15
+
16
+ res.body =~ DEBUG_LOG_PATTERN ? true : false
17
+ end
18
+
19
+ # @param [ String ] path
20
+ # @param [ Hash ] params The request params
21
+ #
22
+ # @return [ Boolean ] true if url(path) contains a FPD, false otherwise
23
+ def full_path_disclosure?(path = nil, params = {})
24
+ !full_path_disclosure_entries(path, params).empty?
25
+ end
26
+
27
+ # @param [ String ] path
28
+ # @param [ Hash ] params The request params
29
+ #
30
+ # @return [ Array<String> ] The FPD found, or an empty array if none
31
+ def full_path_disclosure_entries(path = nil, params = {})
32
+ res = NS::Browser.get(url(path), params)
33
+
34
+ res.body.scan(FPD_PATTERN).flatten
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,35 @@
1
+ %w(custom_directories).each do |required|
2
+ require "cms_scanner/target/platform/wordpress/#{required}"
3
+ end
4
+
5
+ module CMSScanner
6
+ class Target < WebSite
7
+ module Platform
8
+ # Some WordPress specific implementation
9
+ module WordPress
10
+ include PHP
11
+
12
+ WORDPRESS_PATTERN = %r{/(?:(?:wp-content/(?:themes|plugins|plugins))|wp-includes)/}i
13
+
14
+ def wordpress?
15
+ page = Nokogiri::HTML(Browser.get(url).body)
16
+
17
+ page.css('script, link').each do |tag|
18
+ tag_url = tag.attribute('href').to_s
19
+
20
+ next unless in_scope?(tag_url)
21
+
22
+ tag_uri = Addressable::URI.parse(tag_url)
23
+
24
+ return true if tag_uri.path =~ WORDPRESS_PATTERN
25
+ end
26
+ false
27
+ end
28
+
29
+ def wordpress_hosted?
30
+ uri.host =~ /wordpress.com$/i ? true : false
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,62 @@
1
+ module CMSScanner
2
+ class Target < WebSite
3
+ module Platform
4
+ # wp-content & plugins directory implementation
5
+ module WordPress
6
+ def content_dir=(dir)
7
+ @content_dir = dir.chomp('/')
8
+ end
9
+
10
+ def plugins_dir=(dir)
11
+ @plugins_dir = dir.chomp('/')
12
+ end
13
+
14
+ # @return [ String ] The wp-content directory
15
+ def content_dir
16
+ unless @content_dir
17
+ page = Nokogiri::HTML(Browser.get(url).body)
18
+ escaped_url = Regexp.escape(url).gsub(/https?/i, 'https?')
19
+ pattern = %r{#{escaped_url}(.+?)\/(?:themes|plugins|uploads)\/}i
20
+
21
+ page.css('link,script,style,img').each do |tag|
22
+ %w(href src).each do |attribute|
23
+ attr_value = tag.attribute(attribute).to_s
24
+
25
+ next if attr_value.nil? || attr_value.empty?
26
+ next unless in_scope?(attr_value) && attr_value.match(pattern)
27
+
28
+ return @content_dir = Regexp.last_match[1]
29
+ end
30
+ end
31
+ end
32
+ @content_dir
33
+ end
34
+
35
+ # @return [ Addressable::URI ]
36
+ def content_uri
37
+ uri.join("#{content_dir}/")
38
+ end
39
+
40
+ # @return [ String ]
41
+ def content_url
42
+ content_uri.to_s
43
+ end
44
+
45
+ # @return [ String ]
46
+ def plugins_dir
47
+ @plugins_dir ||= "#{content_dir}/plugins"
48
+ end
49
+
50
+ # @return [ Addressable::URI ]
51
+ def plugins_uri
52
+ uri.join("#{plugins_dir}/")
53
+ end
54
+
55
+ # @return [ String ]
56
+ def plugins_url
57
+ plugins_uri.to_s
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,3 @@
1
+ require 'cms_scanner/target/server/generic'
2
+ require 'cms_scanner/target/server/apache'
3
+ require 'cms_scanner/target/server/iis'
@@ -0,0 +1,43 @@
1
+ module CMSScanner
2
+ class Target < WebSite
3
+ module Server
4
+ # Some Apche specific implementation
5
+ module Apache
6
+ # @param [ String ] path
7
+ # @param [ Hash ] params The request params
8
+ #
9
+ # @return [ Symbol ] :apache
10
+ def server(_path = nil, _params = {})
11
+ :Apache
12
+ end
13
+
14
+ # @param [ String ] path
15
+ # @param [ Hash ] params The request params
16
+ #
17
+ # @return [ Boolean ] true if url(path) has the directory
18
+ # listing enabled, false otherwise
19
+ def directory_listing?(path = nil, params = {})
20
+ res = NS::Browser.get(url(path), params)
21
+
22
+ res.code == 200 && res.body =~ /<h1>Index of/ ? true : false
23
+ end
24
+
25
+ # @param [ String ] path
26
+ # @param [ Hash ] params The request params
27
+ #
28
+ # @return [ Array<String> ] The first level of directories/files listed,
29
+ # or an empty array if none
30
+ def directory_listing_entries(path = nil, params = {})
31
+ return [] unless directory_listing?(path, params)
32
+
33
+ doc = Nokogiri::HTML(NS::Browser.get(url(path), params).body)
34
+ found = []
35
+
36
+ doc.css('td a').each { |node| found << node.text.to_s }
37
+
38
+ found[1..-1] # returns the array w/o the first element 'Parent Directory'
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,34 @@
1
+ module CMSScanner
2
+ class Target < WebSite
3
+ module Server
4
+ # Some Apche specific implementation
5
+ module Generic
6
+ # @param [ String ] path
7
+ # @param [ Hash ] params The request params
8
+ #
9
+ # @return [ Symbol ] The detected remote server (:Apache, :IIS)
10
+ def server(path = nil, params = {})
11
+ headers = headers(path, params)
12
+
13
+ return nil unless headers
14
+
15
+ case headers[:server]
16
+ when /\Aapache/i
17
+ :Apache
18
+ when /\AMicrosoft-IIS/i
19
+ :IIS
20
+ end
21
+ end
22
+
23
+ # @param [ String ] path
24
+ # @param [ Hash ] params The request params
25
+ #
26
+ # @return [ Hash ] The headers
27
+ def headers(path = nil, params = {})
28
+ # The HEAD method might be rejected by some servers ... maybe switch to GET ?
29
+ NS::Browser.head(url(path), params).headers
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,48 @@
1
+ module CMSScanner
2
+ class Target < WebSite
3
+ module Server
4
+ # Some IIS specific implementation
5
+ module IIS
6
+ # @param [ String ] path
7
+ # @param [ Hash ] params The request params
8
+ #
9
+ # @return [ Symbol ] :iis
10
+ def server(_path = nil, _params = {})
11
+ :IIS
12
+ end
13
+
14
+ # @param [ String ] path
15
+ # @param [ Hash ] params The request params
16
+ #
17
+ # @return [ Boolean ] true if url(path) has the directory
18
+ # listing enabled, false otherwise
19
+ def directory_listing?(path = nil, params = {})
20
+ res = NS::Browser.get(url(path), params)
21
+
22
+ res.code == 200 && res.body =~ /<H1>#{uri.host} - \// ? true : false
23
+ end
24
+
25
+ # @param [ String ] path
26
+ # @param [ Hash ] params The request params
27
+ #
28
+ # @return [ Array<String> ] The first level of directories/files listed,
29
+ # or an empty array if none
30
+ def directory_listing_entries(path = nil, params = {})
31
+ return [] unless directory_listing?(path, params)
32
+
33
+ doc = Nokogiri::HTML(NS::Browser.get(url(path), params).body)
34
+ found = []
35
+
36
+ doc.css('pre a').each do |node|
37
+ entry = node.text.to_s
38
+
39
+ next if entry == '[To Parent Directory]'
40
+ found << entry
41
+ end
42
+
43
+ found
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end