cms_scanner 0.0.41.10 → 0.0.42.0

Sign up to get free protection for your applications and to get access to all the features.
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