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.
- checksums.yaml +4 -4
- data/app/app.rb +23 -4
- data/app/controllers/core.rb +8 -6
- data/app/controllers/core/cli_options.rb +2 -0
- data/app/controllers/interesting_findings.rb +2 -0
- data/app/finders/interesting_findings.rb +2 -0
- data/app/finders/interesting_findings/fantastico_fileslist.rb +6 -8
- data/app/finders/interesting_findings/headers.rb +3 -1
- data/app/finders/interesting_findings/robots_txt.rb +5 -7
- data/app/finders/interesting_findings/search_replace_db_2.rb +8 -10
- data/app/finders/interesting_findings/xml_rpc.rb +8 -6
- data/app/formatters/cli.rb +2 -0
- data/app/formatters/cli_no_color.rb +2 -0
- data/app/formatters/cli_no_colour.rb +2 -0
- data/app/formatters/json.rb +2 -0
- data/app/models/fantastico_fileslist.rb +16 -12
- data/app/models/headers.rb +29 -25
- data/app/models/interesting_finding.rb +44 -40
- data/app/models/robots_txt.rb +18 -14
- data/app/models/user.rb +25 -21
- data/app/models/version.rb +45 -41
- data/app/models/xml_rpc.rb +58 -54
- data/lib/cms_scanner.rb +5 -85
- data/lib/cms_scanner/browser.rb +2 -0
- data/lib/cms_scanner/browser/actions.rb +13 -13
- data/lib/cms_scanner/browser/options.rb +2 -0
- data/lib/cms_scanner/cache/file_store.rb +2 -0
- data/lib/cms_scanner/cache/typhoeus.rb +2 -0
- data/lib/cms_scanner/controller.rb +2 -0
- data/lib/cms_scanner/controllers.rb +3 -1
- data/lib/cms_scanner/errors.rb +11 -0
- data/lib/cms_scanner/errors/http.rb +52 -51
- data/lib/cms_scanner/errors/scan.rb +10 -6
- data/lib/cms_scanner/exit_code.rb +2 -0
- data/lib/cms_scanner/finders.rb +2 -0
- data/lib/cms_scanner/finders/base_finders.rb +2 -0
- data/lib/cms_scanner/finders/finder.rb +3 -1
- data/lib/cms_scanner/finders/finder/breadth_first_dictionary_attack.rb +3 -1
- data/lib/cms_scanner/finders/finder/enumerator.rb +44 -15
- data/lib/cms_scanner/finders/finder/fingerprinter.rb +9 -21
- data/lib/cms_scanner/finders/finder/smart_url_checker.rb +2 -0
- data/lib/cms_scanner/finders/finder/smart_url_checker/findings.rb +2 -0
- data/lib/cms_scanner/finders/finding.rb +2 -0
- data/lib/cms_scanner/finders/findings.rb +2 -0
- data/lib/cms_scanner/finders/independent_finder.rb +2 -0
- data/lib/cms_scanner/finders/independent_finders.rb +2 -0
- data/lib/cms_scanner/finders/same_type_finder.rb +2 -0
- data/lib/cms_scanner/finders/same_type_finders.rb +2 -0
- data/lib/cms_scanner/finders/unique_finder.rb +2 -0
- data/lib/cms_scanner/finders/unique_finders.rb +2 -0
- data/lib/cms_scanner/formatter.rb +2 -0
- data/lib/cms_scanner/formatter/buffer.rb +3 -1
- data/lib/cms_scanner/helper.rb +2 -0
- data/lib/cms_scanner/numeric.rb +2 -0
- data/lib/cms_scanner/progressbar_null_output.rb +2 -0
- data/lib/cms_scanner/public_suffix/domain.rb +2 -0
- data/lib/cms_scanner/references.rb +2 -0
- data/lib/cms_scanner/scan.rb +86 -0
- data/lib/cms_scanner/target.rb +2 -0
- data/lib/cms_scanner/target/hashes.rb +2 -0
- data/lib/cms_scanner/target/platform.rb +2 -0
- data/lib/cms_scanner/target/platform/php.rb +4 -2
- data/lib/cms_scanner/target/scope.rb +2 -0
- data/lib/cms_scanner/target/server.rb +2 -0
- data/lib/cms_scanner/target/server/apache.rb +2 -0
- data/lib/cms_scanner/target/server/generic.rb +2 -0
- data/lib/cms_scanner/target/server/iis.rb +2 -0
- data/lib/cms_scanner/target/server/nginx.rb +2 -0
- data/lib/cms_scanner/typhoeus/hydra.rb +2 -0
- data/lib/cms_scanner/typhoeus/response.rb +2 -0
- data/lib/cms_scanner/version.rb +3 -1
- data/lib/cms_scanner/vulnerability.rb +2 -0
- data/lib/cms_scanner/web_site.rb +34 -2
- metadata +4 -6
- data/app/controllers.rb +0 -2
- data/app/finders.rb +0 -1
- data/app/formatters.rb +0 -4
- data/app/models.rb +0 -7
data/app/models/robots_txt.rb
CHANGED
@@ -1,22 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module CMSScanner
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
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
|
-
|
11
|
-
|
13
|
+
entries.each do |entry|
|
14
|
+
next unless entry =~ /\A(?:dis)?allow:\s*(.+)\z/i
|
12
15
|
|
13
|
-
|
14
|
-
|
16
|
+
match = Regexp.last_match(1)
|
17
|
+
next if match == '/'
|
15
18
|
|
16
|
-
|
17
|
-
|
19
|
+
results << match
|
20
|
+
end
|
18
21
|
|
19
|
-
|
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
|
-
|
3
|
-
|
4
|
-
|
4
|
+
module Model
|
5
|
+
# User
|
6
|
+
class User
|
7
|
+
include Finders::Finding
|
5
8
|
|
6
|
-
|
7
|
-
|
9
|
+
attr_accessor :password
|
10
|
+
attr_reader :id, :username
|
8
11
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
19
|
-
|
21
|
+
parse_finding_options(opts)
|
22
|
+
end
|
20
23
|
|
21
|
-
|
22
|
-
|
24
|
+
def ==(other)
|
25
|
+
return false unless self.class == other.class
|
23
26
|
|
24
|
-
|
25
|
-
|
27
|
+
username == other.username && password == other.password
|
28
|
+
end
|
26
29
|
|
27
|
-
|
28
|
-
|
30
|
+
def to_s
|
31
|
+
username
|
32
|
+
end
|
29
33
|
end
|
30
34
|
end
|
31
35
|
end
|
data/app/models/version.rb
CHANGED
@@ -1,45 +1,49 @@
|
|
1
|
-
|
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
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
data/app/models/xml_rpc.rb
CHANGED
@@ -1,69 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module CMSScanner
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
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
|
-
|
10
|
-
|
11
|
-
|
12
|
+
# @return [ Array<String> ]
|
13
|
+
def available_methods
|
14
|
+
return @available_methods if @available_methods
|
12
15
|
|
13
|
-
|
16
|
+
@available_methods = []
|
14
17
|
|
15
|
-
|
16
|
-
|
18
|
+
res = method_call('system.listMethods').run
|
19
|
+
doc = Nokogiri::XML.parse(res.body)
|
17
20
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
+
doc.search('methodResponse params param value array data value string').each do |s|
|
22
|
+
@available_methods << s.text
|
23
|
+
end
|
21
24
|
|
22
|
-
|
23
|
-
|
25
|
+
@available_methods
|
26
|
+
end
|
24
27
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
28
|
+
# @return [ Boolean ] Whether or not the XMLRPC is enabled
|
29
|
+
def enabled?
|
30
|
+
!available_methods.empty?
|
31
|
+
end
|
29
32
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
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"
|
data/lib/cms_scanner/browser.rb
CHANGED
@@ -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::
|
9
|
-
def
|
10
|
-
|
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
|
18
|
-
|
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
|
26
|
-
|
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
|
34
|
-
|
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
|
44
|
-
|
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
|