cms_scanner 0.0.41.10 → 0.0.42.0

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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/app/app.rb +23 -4
  3. data/app/controllers/core.rb +8 -6
  4. data/app/controllers/core/cli_options.rb +2 -0
  5. data/app/controllers/interesting_findings.rb +2 -0
  6. data/app/finders/interesting_findings.rb +2 -0
  7. data/app/finders/interesting_findings/fantastico_fileslist.rb +6 -8
  8. data/app/finders/interesting_findings/headers.rb +3 -1
  9. data/app/finders/interesting_findings/robots_txt.rb +5 -7
  10. data/app/finders/interesting_findings/search_replace_db_2.rb +8 -10
  11. data/app/finders/interesting_findings/xml_rpc.rb +8 -6
  12. data/app/formatters/cli.rb +2 -0
  13. data/app/formatters/cli_no_color.rb +2 -0
  14. data/app/formatters/cli_no_colour.rb +2 -0
  15. data/app/formatters/json.rb +2 -0
  16. data/app/models/fantastico_fileslist.rb +16 -12
  17. data/app/models/headers.rb +29 -25
  18. data/app/models/interesting_finding.rb +44 -40
  19. data/app/models/robots_txt.rb +18 -14
  20. data/app/models/user.rb +25 -21
  21. data/app/models/version.rb +45 -41
  22. data/app/models/xml_rpc.rb +58 -54
  23. data/lib/cms_scanner.rb +5 -85
  24. data/lib/cms_scanner/browser.rb +2 -0
  25. data/lib/cms_scanner/browser/actions.rb +13 -13
  26. data/lib/cms_scanner/browser/options.rb +2 -0
  27. data/lib/cms_scanner/cache/file_store.rb +2 -0
  28. data/lib/cms_scanner/cache/typhoeus.rb +2 -0
  29. data/lib/cms_scanner/controller.rb +2 -0
  30. data/lib/cms_scanner/controllers.rb +3 -1
  31. data/lib/cms_scanner/errors.rb +11 -0
  32. data/lib/cms_scanner/errors/http.rb +52 -51
  33. data/lib/cms_scanner/errors/scan.rb +10 -6
  34. data/lib/cms_scanner/exit_code.rb +2 -0
  35. data/lib/cms_scanner/finders.rb +2 -0
  36. data/lib/cms_scanner/finders/base_finders.rb +2 -0
  37. data/lib/cms_scanner/finders/finder.rb +3 -1
  38. data/lib/cms_scanner/finders/finder/breadth_first_dictionary_attack.rb +3 -1
  39. data/lib/cms_scanner/finders/finder/enumerator.rb +44 -15
  40. data/lib/cms_scanner/finders/finder/fingerprinter.rb +9 -21
  41. data/lib/cms_scanner/finders/finder/smart_url_checker.rb +2 -0
  42. data/lib/cms_scanner/finders/finder/smart_url_checker/findings.rb +2 -0
  43. data/lib/cms_scanner/finders/finding.rb +2 -0
  44. data/lib/cms_scanner/finders/findings.rb +2 -0
  45. data/lib/cms_scanner/finders/independent_finder.rb +2 -0
  46. data/lib/cms_scanner/finders/independent_finders.rb +2 -0
  47. data/lib/cms_scanner/finders/same_type_finder.rb +2 -0
  48. data/lib/cms_scanner/finders/same_type_finders.rb +2 -0
  49. data/lib/cms_scanner/finders/unique_finder.rb +2 -0
  50. data/lib/cms_scanner/finders/unique_finders.rb +2 -0
  51. data/lib/cms_scanner/formatter.rb +2 -0
  52. data/lib/cms_scanner/formatter/buffer.rb +3 -1
  53. data/lib/cms_scanner/helper.rb +2 -0
  54. data/lib/cms_scanner/numeric.rb +2 -0
  55. data/lib/cms_scanner/progressbar_null_output.rb +2 -0
  56. data/lib/cms_scanner/public_suffix/domain.rb +2 -0
  57. data/lib/cms_scanner/references.rb +2 -0
  58. data/lib/cms_scanner/scan.rb +86 -0
  59. data/lib/cms_scanner/target.rb +2 -0
  60. data/lib/cms_scanner/target/hashes.rb +2 -0
  61. data/lib/cms_scanner/target/platform.rb +2 -0
  62. data/lib/cms_scanner/target/platform/php.rb +4 -2
  63. data/lib/cms_scanner/target/scope.rb +2 -0
  64. data/lib/cms_scanner/target/server.rb +2 -0
  65. data/lib/cms_scanner/target/server/apache.rb +2 -0
  66. data/lib/cms_scanner/target/server/generic.rb +2 -0
  67. data/lib/cms_scanner/target/server/iis.rb +2 -0
  68. data/lib/cms_scanner/target/server/nginx.rb +2 -0
  69. data/lib/cms_scanner/typhoeus/hydra.rb +2 -0
  70. data/lib/cms_scanner/typhoeus/response.rb +2 -0
  71. data/lib/cms_scanner/version.rb +3 -1
  72. data/lib/cms_scanner/vulnerability.rb +2 -0
  73. data/lib/cms_scanner/web_site.rb +34 -2
  74. metadata +4 -6
  75. data/app/controllers.rb +0 -2
  76. data/app/finders.rb +0 -1
  77. data/app/formatters.rb +0 -4
  78. data/app/models.rb +0 -7
@@ -1,22 +1,26 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CMSScanner
2
- # Robots.txt
3
- class RobotsTxt < InterestingFinding
4
- # @todo Better detection, currently everything not empty or / is returned
5
- #
6
- # @return [ Array<String> ] The interesting Allow/Disallow rules detected
7
- def interesting_entries
8
- results = []
4
+ module Model
5
+ # Robots.txt
6
+ class RobotsTxt < InterestingFinding
7
+ # @todo Better detection, currently everything not empty or / is returned
8
+ #
9
+ # @return [ Array<String> ] The interesting Allow/Disallow rules detected
10
+ def interesting_entries
11
+ results = []
9
12
 
10
- entries.each do |entry|
11
- next unless entry =~ /\A(?:dis)?allow:\s*(.+)\z/i
13
+ entries.each do |entry|
14
+ next unless entry =~ /\A(?:dis)?allow:\s*(.+)\z/i
12
15
 
13
- match = Regexp.last_match(1)
14
- next if match == '/'
16
+ match = Regexp.last_match(1)
17
+ next if match == '/'
15
18
 
16
- results << match
17
- end
19
+ results << match
20
+ end
18
21
 
19
- results.uniq
22
+ results.uniq
23
+ end
20
24
  end
21
25
  end
22
26
  end
data/app/models/user.rb CHANGED
@@ -1,31 +1,35 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CMSScanner
2
- # User
3
- class User
4
- include Finders::Finding
4
+ module Model
5
+ # User
6
+ class User
7
+ include Finders::Finding
5
8
 
6
- attr_accessor :password
7
- attr_reader :id, :username
9
+ attr_accessor :password
10
+ attr_reader :id, :username
8
11
 
9
- # @param [ String ] username
10
- # @param [ Hash ] opts
11
- # @option opts [ Integer ] :id
12
- # @option opts [ String ] :password
13
- def initialize(username, opts = {})
14
- @username = username
15
- @password = opts[:password]
16
- @id = opts[:id]
12
+ # @param [ String ] username
13
+ # @param [ Hash ] opts
14
+ # @option opts [ Integer ] :id
15
+ # @option opts [ String ] :password
16
+ def initialize(username, opts = {})
17
+ @username = username
18
+ @password = opts[:password]
19
+ @id = opts[:id]
17
20
 
18
- parse_finding_options(opts)
19
- end
21
+ parse_finding_options(opts)
22
+ end
20
23
 
21
- def ==(other)
22
- return false unless self.class == other.class
24
+ def ==(other)
25
+ return false unless self.class == other.class
23
26
 
24
- username == other.username && password == other.password
25
- end
27
+ username == other.username && password == other.password
28
+ end
26
29
 
27
- def to_s
28
- username
30
+ def to_s
31
+ username
32
+ end
29
33
  end
30
34
  end
31
35
  end
@@ -1,45 +1,49 @@
1
- module CMSScanner
2
- # Version
3
- class Version
4
- include Finders::Finding
5
-
6
- attr_reader :number
7
-
8
- def initialize(number, opts = {})
9
- @number = number.to_s
10
- @number = "0#{number}" if @number[0, 1] == '.'
11
-
12
- parse_finding_options(opts)
13
- end
14
-
15
- # @param [ Version, String ] other
16
- # rubocop:disable Style/NumericPredicate
17
- def ==(other)
18
- (self <=> other) == 0
19
- end
20
- # rubocop:enable all
1
+ # frozen_string_literal: true
21
2
 
22
- # @param [ Version, String ] other
23
- def <(other)
24
- (self <=> other) == -1
25
- end
26
-
27
- # @param [ Version, String ] other
28
- def >(other)
29
- (self <=> other) == 1
30
- end
31
-
32
- # @param [ Version, String ] other
33
- def <=>(other)
34
- other = self.class.new(other) unless other.is_a?(self.class) # handle potential '.1' version
35
-
36
- Gem::Version.new(number) <=> Gem::Version.new(other.number)
37
- rescue ArgumentError
38
- false
39
- end
40
-
41
- def to_s
42
- number
3
+ module CMSScanner
4
+ module Model
5
+ # Version
6
+ class Version
7
+ include Finders::Finding
8
+
9
+ attr_reader :number
10
+
11
+ def initialize(number, opts = {})
12
+ @number = number.to_s
13
+ @number = "0#{number}" if @number[0, 1] == '.'
14
+
15
+ parse_finding_options(opts)
16
+ end
17
+
18
+ # @param [ Version, String ] other
19
+ # rubocop:disable Style/NumericPredicate
20
+ def ==(other)
21
+ (self <=> other) == 0
22
+ end
23
+ # rubocop:enable all
24
+
25
+ # @param [ Version, String ] other
26
+ def <(other)
27
+ (self <=> other) == -1
28
+ end
29
+
30
+ # @param [ Version, String ] other
31
+ def >(other)
32
+ (self <=> other) == 1
33
+ end
34
+
35
+ # @param [ Version, String ] other
36
+ def <=>(other)
37
+ other = self.class.new(other) unless other.is_a?(self.class) # handle potential '.1' version
38
+
39
+ Gem::Version.new(number) <=> Gem::Version.new(other.number)
40
+ rescue ArgumentError
41
+ false
42
+ end
43
+
44
+ def to_s
45
+ number
46
+ end
43
47
  end
44
48
  end
45
49
  end
@@ -1,69 +1,73 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CMSScanner
2
- # XML RPC
3
- class XMLRPC < InterestingFinding
4
- # @return [ Browser ]
5
- def browser
6
- @browser ||= NS::Browser.instance
7
- end
4
+ module Model
5
+ # XML RPC
6
+ class XMLRPC < InterestingFinding
7
+ # @return [ Browser ]
8
+ def browser
9
+ @browser ||= NS::Browser.instance
10
+ end
8
11
 
9
- # @return [ Array<String> ]
10
- def available_methods
11
- return @available_methods if @available_methods
12
+ # @return [ Array<String> ]
13
+ def available_methods
14
+ return @available_methods if @available_methods
12
15
 
13
- @available_methods = []
16
+ @available_methods = []
14
17
 
15
- res = method_call('system.listMethods').run
16
- doc = Nokogiri::XML.parse(res.body)
18
+ res = method_call('system.listMethods').run
19
+ doc = Nokogiri::XML.parse(res.body)
17
20
 
18
- doc.search('methodResponse params param value array data value string').each do |s|
19
- @available_methods << s.text
20
- end
21
+ doc.search('methodResponse params param value array data value string').each do |s|
22
+ @available_methods << s.text
23
+ end
21
24
 
22
- @available_methods
23
- end
25
+ @available_methods
26
+ end
24
27
 
25
- # @return [ Boolean ] Whether or not the XMLRPC is enabled
26
- def enabled?
27
- !available_methods.empty?
28
- end
28
+ # @return [ Boolean ] Whether or not the XMLRPC is enabled
29
+ def enabled?
30
+ !available_methods.empty?
31
+ end
29
32
 
30
- # @param [ String ] method_name
31
- # @param [ Array ] method_params
32
- # @param [ Hash ] request_params
33
- #
34
- # @return [ Typhoeus::Request ]
35
- def method_call(method_name, method_params = [], request_params = {})
36
- browser.forge_request(
37
- url,
38
- request_params.merge(
39
- method: :post,
40
- body: ::XMLRPC::Create.new.methodCall(method_name, *method_params)
33
+ # @param [ String ] method_name
34
+ # @param [ Array ] method_params
35
+ # @param [ Hash ] request_params
36
+ #
37
+ # @return [ Typhoeus::Request ]
38
+ def method_call(method_name, method_params = [], request_params = {})
39
+ browser.forge_request(
40
+ url,
41
+ request_params.merge(
42
+ method: :post,
43
+ body: ::XMLRPC::Create.new.methodCall(method_name, *method_params)
44
+ )
41
45
  )
42
- )
43
- end
46
+ end
44
47
 
45
- # @param [ Array<Array> ] methods_and_params
46
- # @param [ Hash ] request_params
47
- #
48
- # Example of methods_and_params:
49
- # [
50
- # [method1, param1, param2],
51
- # [method2, param1],
52
- # [method3]
53
- # ]
54
- #
55
- # @return [ Typhoeus::Request ]
56
- def multi_call(methods_and_params = [], request_params = {})
57
- browser.forge_request(
58
- url,
59
- request_params.merge(
60
- method: :post,
61
- body: ::XMLRPC::Create.new.methodCall(
62
- 'system.multicall',
63
- methods_and_params.collect { |m| { methodName: m[0], params: m[1..-1] } }
48
+ # @param [ Array<Array> ] methods_and_params
49
+ # @param [ Hash ] request_params
50
+ #
51
+ # Example of methods_and_params:
52
+ # [
53
+ # [method1, param1, param2],
54
+ # [method2, param1],
55
+ # [method3]
56
+ # ]
57
+ #
58
+ # @return [ Typhoeus::Request ]
59
+ def multi_call(methods_and_params = [], request_params = {})
60
+ browser.forge_request(
61
+ url,
62
+ request_params.merge(
63
+ method: :post,
64
+ body: ::XMLRPC::Create.new.methodCall(
65
+ 'system.multicall',
66
+ methods_and_params.collect { |m| { methodName: m[0], params: m[1..-1] } }
67
+ )
64
68
  )
65
69
  )
66
- )
70
+ end
67
71
  end
68
72
  end
69
73
  end
data/lib/cms_scanner.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Gems
2
4
  require 'typhoeus'
3
5
  require 'nokogiri'
@@ -15,16 +17,16 @@ require 'fileutils'
15
17
  require 'pathname'
16
18
  require 'timeout'
17
19
  require 'xmlrpc/client'
18
- # Monkey Patches
20
+ # Monkey Patches/Fixes
19
21
  require 'cms_scanner/typhoeus/response' # Adds a Response#html using Nokogiri to parse the body
20
22
  require 'cms_scanner/typhoeus/hydra' # https://github.com/typhoeus/typhoeus/issues/439
21
23
  require 'cms_scanner/public_suffix/domain' # Adds a Domain#match method and logic, used in scope stuff
22
24
  require 'cms_scanner/numeric' # Adds a Numeric#bytes_to_human
23
25
  # Custom Libs
26
+ require 'cms_scanner/scan'
24
27
  require 'cms_scanner/helper'
25
28
  require 'cms_scanner/exit_code'
26
- require 'cms_scanner/errors/http'
27
- require 'cms_scanner/errors/scan'
29
+ require 'cms_scanner/errors'
28
30
  require 'cms_scanner/cache/typhoeus'
29
31
  require 'cms_scanner/target'
30
32
  require 'cms_scanner/browser'
@@ -121,88 +123,6 @@ module CMSScanner
121
123
  base.extend(ClassMethods)
122
124
  super(base)
123
125
  end
124
-
125
- # Scan
126
- class Scan
127
- attr_reader :run_error
128
-
129
- def initialize
130
- controllers << NS::Controller::Core.new
131
-
132
- exit_hook
133
-
134
- yield self if block_given?
135
- end
136
-
137
- # @return [ Controllers ]
138
- def controllers
139
- @controllers ||= NS::Controllers.new
140
- end
141
-
142
- def run
143
- controllers.run
144
- rescue OptParseValidator::NoRequiredOption => e
145
- @run_error = e
146
-
147
- formatter.output('@usage', msg: e.message)
148
- rescue NoMemoryError, ScriptError, SecurityError, SignalException, StandardError, SystemStackError => e
149
- @run_error = e
150
-
151
- formatter.output('@scan_aborted',
152
- reason: e.is_a?(Interrupt) ? 'Canceled by User' : e.message,
153
- trace: e.backtrace,
154
- verbose: controllers.first.parsed_options[:verbose] ||
155
- run_error_exit_code == NS::ExitCode::EXCEPTION)
156
- ensure
157
- Browser.instance.hydra.abort
158
-
159
- formatter.beautify
160
- end
161
-
162
- # Used for convenience
163
- # @See Formatter
164
- def formatter
165
- controllers.first.formatter
166
- end
167
-
168
- # @return [ Hash ]
169
- def datastore
170
- controllers.first.datastore
171
- end
172
-
173
- # Hook to be able to have an exit code returned
174
- # depending on the findings / errors
175
- # :nocov:
176
- def exit_hook
177
- # Avoid hooking the exit when rspec is running, otherwise it will always return 0
178
- # and Travis won't detect failed builds. Couldn't find a better way, even though
179
- # some people managed to https://github.com/rspec/rspec-core/pull/410
180
- return if defined?(RSpec)
181
-
182
- at_exit do
183
- exit(run_error_exit_code) if run_error
184
-
185
- controller = controllers.first
186
-
187
- # The parsed_option[:url] must be checked to avoid raising erros when only -h/-v are given
188
- exit(NS::ExitCode::VULNERABLE) if controller.parsed_options[:url] && controller.target.vulnerable?
189
- exit(NS::ExitCode::OK)
190
- end
191
- end
192
- # :nocov:
193
-
194
- # @return [ Integer ] The exit code related to the run_error
195
- def run_error_exit_code
196
- return NS::ExitCode::CLI_OPTION_ERROR if run_error.is_a?(OptParseValidator::Error) ||
197
- run_error.is_a?(OptionParser::ParseError)
198
-
199
- return NS::ExitCode::INTERRUPTED if run_error.is_a?(Interrupt)
200
-
201
- return NS::ExitCode::ERROR if run_error.is_a?(NS::Error) || run_error.is_a?(CMSScanner::Error)
202
-
203
- NS::ExitCode::EXCEPTION
204
- end
205
- end
206
126
  end
207
127
 
208
128
  require "#{CMSScanner::APP_DIR}/app"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'cms_scanner/browser/actions'
2
4
  require 'cms_scanner/browser/options'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CMSScanner
2
4
  class Browser
3
5
  # Browser Actions (get, post etc)
@@ -5,43 +7,41 @@ module CMSScanner
5
7
  # @param [ String ] url
6
8
  # @param [ Hash ] params
7
9
  #
8
- # @return [ Typhoeus::Response ]
9
- def get(url, params = {})
10
- process(url, params.merge(method: :get))
10
+ # @return [ Typhoeus::Request ]
11
+ def forge_request(url, params = {})
12
+ NS::Browser.instance.forge_request(url, params)
11
13
  end
12
14
 
13
15
  # @param [ String ] url
14
16
  # @param [ Hash ] params
15
17
  #
16
18
  # @return [ Typhoeus::Response ]
17
- def post(url, params = {})
18
- process(url, params.merge(method: :post))
19
+ def get(url, params = {})
20
+ forge_request(url, params.merge(method: :get)).run
19
21
  end
20
22
 
21
23
  # @param [ String ] url
22
24
  # @param [ Hash ] params
23
25
  #
24
26
  # @return [ Typhoeus::Response ]
25
- def head(url, params = {})
26
- process(url, params.merge(method: :head))
27
+ def post(url, params = {})
28
+ forge_request(url, params.merge(method: :post)).run
27
29
  end
28
30
 
29
31
  # @param [ String ] url
30
32
  # @param [ Hash ] params
31
33
  #
32
34
  # @return [ Typhoeus::Response ]
33
- def get_and_follow_location(url, params = {})
34
- get(url, { followlocation: true, maxredirs: 3 }.merge(params))
35
+ def head(url, params = {})
36
+ forge_request(url, params.merge(method: :head)).run
35
37
  end
36
38
 
37
- protected
38
-
39
39
  # @param [ String ] url
40
40
  # @param [ Hash ] params
41
41
  #
42
42
  # @return [ Typhoeus::Response ]
43
- def process(url, params)
44
- Typhoeus::Request.new(url, NS::Browser.instance.request_params(params)).run
43
+ def get_and_follow_location(url, params = {})
44
+ get(url, { followlocation: true, maxredirs: 3 }.merge(params))
45
45
  end
46
46
  end
47
47
  end