Update upstream source from tag 'upstream/0.0.44.1'
Update to upstream version '0.0.44.1'
with Debian dir 72674ec701df28b981453186297e37b36a2217ca
Sophie Brun
5 years ago
0 | 0 | AllCops: |
1 | TargetRubyVersion: 2.3 | |
1 | TargetRubyVersion: 2.4 | |
2 | 2 | Exclude: |
3 | 3 | - '*.gemspec' |
4 | 4 | - 'vendor/**/*' |
9 | 9 | Max: 120 |
10 | 10 | MethodLength: |
11 | 11 | Max: 18 |
12 | Exclude: | |
13 | - app/controllers/core/cli_options.rb | |
12 | 14 | Lint/UriEscapeUnescape: |
13 | 15 | Enabled: false |
14 | 16 | Metrics/AbcSize: |
1 | 1 | sudo: false |
2 | 2 | cache: bundler |
3 | 3 | rvm: |
4 | - 2.3.0 | |
5 | - 2.3.1 | |
6 | - 2.3.2 | |
7 | - 2.3.3 | |
8 | - 2.3.4 | |
9 | - 2.3.5 | |
10 | - 2.3.6 | |
11 | - 2.3.7 | |
12 | - 2.3.8 | |
13 | 4 | - 2.4.1 |
14 | 5 | - 2.4.2 |
15 | 6 | - 2.4.3 |
16 | 7 | - 2.4.4 |
17 | 8 | - 2.4.5 |
9 | - 2.4.6 | |
18 | 10 | - 2.5.0 |
19 | 11 | - 2.5.1 |
20 | 12 | - 2.5.2 |
45 | 45 | OptBoolean.new(['--random-user-agent', '--rua', |
46 | 46 | 'Use a random user-agent for each scan']), |
47 | 47 | OptFilePath.new(['--user-agents-list FILE-PATH', |
48 | 'List of agents to use with --random-user-agent'], exists: true, advanced: true), | |
48 | 'List of agents to use with --random-user-agent'], | |
49 | exists: true, | |
50 | advanced: true, | |
51 | default: APP_DIR.join('user_agents.txt')), | |
49 | 52 | OptCredentials.new(['--http-auth login:password']), |
50 | 53 | OptPositiveInteger.new(['-t', '--max-threads VALUE', 'The max threads to use'], |
51 | 54 | default: 5), |
6 | 6 | # Core Controller |
7 | 7 | class Core < Base |
8 | 8 | def setup_cache |
9 | return unless parsed_options[:cache_dir] | |
9 | return unless NS::ParsedCli.cache_dir | |
10 | 10 | |
11 | storage_path = File.join(parsed_options[:cache_dir], Digest::MD5.hexdigest(target.url)) | |
11 | storage_path = File.join(NS::ParsedCli.cache_dir, Digest::MD5.hexdigest(target.url)) | |
12 | 12 | |
13 | 13 | Typhoeus::Config.cache = Cache::Typhoeus.new(storage_path) |
14 | Typhoeus::Config.cache.clean if parsed_options[:clear_cache] | |
14 | Typhoeus::Config.cache.clean if NS::ParsedCli.clear_cache | |
15 | 15 | end |
16 | 16 | |
17 | 17 | def before_scan |
22 | 22 | end |
23 | 23 | |
24 | 24 | def maybe_output_banner_help_and_version |
25 | output('banner') if parsed_options[:banner] | |
26 | output('help', help: option_parser.simple_help, simple: true) if parsed_options[:help] | |
27 | output('help', help: option_parser.full_help, simple: false) if parsed_options[:hh] | |
28 | output('version') if parsed_options[:version] | |
25 | output('banner') if NS::ParsedCli.banner | |
26 | output('help', help: option_parser.simple_help, simple: true) if NS::ParsedCli.help | |
27 | output('help', help: option_parser.full_help, simple: false) if NS::ParsedCli.hh | |
28 | output('version') if NS::ParsedCli.version | |
29 | 29 | |
30 | exit(NS::ExitCode::OK) if parsed_options[:help] || parsed_options[:hh] || parsed_options[:version] | |
30 | exit(NS::ExitCode::OK) if NS::ParsedCli.help || NS::ParsedCli.hh || NS::ParsedCli.version | |
31 | 31 | end |
32 | 32 | |
33 | 33 | # Checks that the target is accessible, raises related errors otherwise |
42 | 42 | when 401 |
43 | 43 | raise Error::HTTPAuthRequired |
44 | 44 | when 403 |
45 | raise Error::AccessForbidden, parsed_options[:random_user_agent] | |
45 | raise Error::AccessForbidden, NS::ParsedCli.random_user_agent | |
46 | 46 | when 407 |
47 | 47 | raise Error::ProxyAuthRequired |
48 | 48 | end |
53 | 53 | |
54 | 54 | return if target.in_scope?(effective_url) |
55 | 55 | |
56 | raise Error::HTTPRedirect, effective_url unless parsed_options[:ignore_main_redirect] | |
56 | raise Error::HTTPRedirect, effective_url unless NS::ParsedCli.ignore_main_redirect | |
57 | 57 | |
58 | 58 | target.homepage_res = res |
59 | 59 | end |
14 | 14 | end |
15 | 15 | |
16 | 16 | def run |
17 | mode = parsed_options[:interesting_findings_detection] || parsed_options[:detection_mode] | |
17 | mode = NS::ParsedCli.interesting_findings_detection || NS::ParsedCli.detection_mode | |
18 | 18 | findings = target.interesting_findings(mode: mode) |
19 | 19 | |
20 | 20 | output('findings', findings: findings) unless findings.empty? |
6 | 6 | s.name = 'cms_scanner' |
7 | 7 | s.version = CMSScanner::VERSION |
8 | 8 | s.platform = Gem::Platform::RUBY |
9 | s.required_ruby_version = '>= 2.3' | |
9 | s.required_ruby_version = '>= 2.4' | |
10 | 10 | s.authors = ['WPScanTeam'] |
11 | 11 | s.email = ['[email protected]'] |
12 | 12 | s.summary = 'CMS Scanner Framework (experimental)' |
31 | 31 | s.add_development_dependency 'rake', '~> 12.3' |
32 | 32 | s.add_development_dependency 'rspec', '~> 3.8.0' |
33 | 33 | s.add_development_dependency 'rspec-its', '~> 1.2.0' |
34 | s.add_development_dependency 'rubocop', '~> 0.66.0' | |
34 | s.add_development_dependency 'rubocop', '~> 0.67.1' | |
35 | 35 | s.add_development_dependency 'simplecov', '~> 0.16.1' |
36 | 36 | s.add_development_dependency 'webmock', '~> 3.5.1' |
37 | 37 | end |
20 | 20 | s.executables = ['cmsscan'] |
21 | 21 | s.require_paths = ['lib'] |
22 | 22 | |
23 | s.add_dependency 'cms_scanner', '~> 0.0.42.0' | |
23 | s.add_dependency 'cms_scanner', '~> 0.0.44.1' | |
24 | 24 | |
25 | 25 | s.add_development_dependency 'bundler', '>= 1.6' |
26 | 26 | s.add_development_dependency 'coveralls', '~> 0.8.0' |
61 | 61 | |
62 | 62 | @user_agents = [] |
63 | 63 | |
64 | # The user_agents_list is managed by the CLI options, with the default being | |
65 | # APP_DIR/user_agents.txt | |
64 | 66 | File.open(user_agents_list).each do |line| |
65 | 67 | next if line == "\n" || line[0, 1] == '#' |
66 | 68 | |
68 | 70 | end |
69 | 71 | |
70 | 72 | @user_agents |
71 | end | |
72 | ||
73 | # @return [ String ] The path to the user agents list | |
74 | def user_agents_list | |
75 | @user_agents_list ||= File.join(APP_DIR, 'user_agents.txt') | |
76 | 73 | end |
77 | 74 | |
78 | 75 | # @param [ value ] The throttle time in milliseconds |
13 | 13 | def initialize(parsed_options = {}) |
14 | 14 | self.throttle = 0 |
15 | 15 | |
16 | load_options(parsed_options) | |
16 | load_options(parsed_options.dup) | |
17 | 17 | end |
18 | 18 | |
19 | 19 | private_class_method :new |
21 | 21 | # Reset all the class attibutes |
22 | 22 | # Currently only used in specs |
23 | 23 | def self.reset |
24 | @@target = nil | |
25 | @@parsed_options = nil | |
26 | @@datastore = nil | |
27 | @@formatter = nil | |
24 | @@target = nil | |
25 | @@datastore = nil | |
26 | @@formatter = nil | |
28 | 27 | end |
29 | 28 | |
30 | 29 | # @return [ Target ] |
31 | 30 | def target |
32 | @@target ||= NS::Target.new(parsed_options[:url], parsed_options) | |
31 | @@target ||= NS::Target.new(NS::ParsedCli.url, NS::ParsedCli.options) | |
33 | 32 | end |
34 | 33 | |
35 | 34 | # @param [ OptParsevalidator::OptParser ] parser |
42 | 41 | @@option_parser |
43 | 42 | end |
44 | 43 | |
45 | # Set the parsed options and initialize the browser | |
46 | # with them | |
47 | # | |
48 | # @param [ Hash ] options | |
49 | def self.parsed_options=(options) | |
50 | @@parsed_options = options | |
51 | ||
52 | NS::Browser.instance(options) | |
53 | end | |
54 | ||
55 | # @return [ Hash ] | |
56 | def parsed_options | |
57 | @@parsed_options ||= {} | |
58 | end | |
59 | ||
60 | 44 | # @return [ Hash ] |
61 | 45 | def datastore |
62 | 46 | @@datastore ||= {} |
64 | 48 | |
65 | 49 | # @return [ Formatter::Base ] |
66 | 50 | def formatter |
67 | @@formatter ||= NS::Formatter.load(parsed_options[:format], datastore[:views]) | |
51 | @@formatter ||= NS::Formatter.load(NS::ParsedCli.format, datastore[:views]) | |
68 | 52 | end |
69 | 53 | |
70 | 54 | # @see Formatter#output |
83 | 67 | |
84 | 68 | # @return [ Boolean ] |
85 | 69 | def user_interaction? |
86 | formatter.user_interaction? && !parsed_options[:output] | |
70 | formatter.user_interaction? && !NS::ParsedCli.output | |
87 | 71 | end |
88 | 72 | |
89 | 73 | # @return [ String ] |
107 | 91 | |
108 | 92 | # @return [ Hash ] All the instance variable keys (and their values) and the verbose value |
109 | 93 | def instance_variable_values |
110 | h = { verbose: parsed_options[:verbose] } | |
94 | h = { verbose: NS::ParsedCli.verbose } | |
111 | 95 | instance_variables.each do |a| |
112 | 96 | s = a.to_s |
113 | 97 | n = s[1..s.size] |
34 | 34 | end |
35 | 35 | |
36 | 36 | def run |
37 | parsed_options = option_parser.results | |
38 | first.class.option_parser = option_parser | |
39 | first.class.parsed_options = parsed_options | |
37 | NS::ParsedCli.options = option_parser.results | |
38 | first.class.option_parser = option_parser # To be able to output the help when -h/--hh | |
40 | 39 | |
41 | redirect_output_to_file(parsed_options[:output]) if parsed_options[:output] | |
40 | redirect_output_to_file(NS::ParsedCli.output) if NS::ParsedCli.output | |
42 | 41 | |
43 | Timeout.timeout(parsed_options[:max_scan_duration], NS::Error::MaxScanDurationReached) do | |
42 | Timeout.timeout(NS::ParsedCli.max_scan_duration, NS::Error::MaxScanDurationReached) do | |
44 | 43 | each(&:before_scan) |
45 | 44 | |
46 | 45 | @running = true |
48 | 47 | each(&:run) |
49 | 48 | end |
50 | 49 | ensure |
51 | Browser.instance.hydra.abort | |
50 | NS::Browser.instance.hydra.abort | |
52 | 51 | |
53 | 52 | # Reverse is used here as the app/controllers/core#after_scan finishes the output |
54 | 53 | # and must be the last one to be executed. It also guarantee that stats will be output |
102 | 102 | 'Request timed out.' |
103 | 103 | elsif response.code.zero? |
104 | 104 | "No response from remote server. WAF/IPS? (#{response.return_message})" |
105 | elsif response.code.to_s =~ /^50/ | |
105 | elsif /^50/.match?(response.code.to_s) | |
106 | 106 | 'Server error, try reducing the number of threads.' |
107 | 107 | else |
108 | 108 | "Unknown response received Code: #{response.code}\nBody: #{response.body}" |
0 | # frozen_string_literal: true | |
1 | ||
2 | module CMSScanner | |
3 | # Class to hold the parsed CLI options and have them available via | |
4 | # methods, such as #verbose?, rather than from the hash. | |
5 | # This is similar to an OpenStruct, but class wise (rather than instance), and with | |
6 | # the logic to update the Browser options accordinly | |
7 | class ParsedCli | |
8 | # @return [ Hash ] | |
9 | def self.options | |
10 | @options ||= {} | |
11 | end | |
12 | ||
13 | # Sets the CLI options, and put them into the Browser as well | |
14 | # @param [ Hash ] options | |
15 | def self.options=(options) | |
16 | @options = options.dup || {} | |
17 | ||
18 | NS::Browser.reset | |
19 | NS::Browser.instance(@options) | |
20 | end | |
21 | ||
22 | # @return [ Boolean ] | |
23 | def self.verbose? | |
24 | options[:verbose] ? true : false | |
25 | end | |
26 | ||
27 | # Unknown methods will return nil, this is the expected behaviour | |
28 | # rubocop:disable Style/MissingRespondToMissing | |
29 | def self.method_missing(method_name, *_args, &_block) | |
30 | super if method_name == :new | |
31 | ||
32 | options[method_name.to_sym] | |
33 | end | |
34 | # rubocop:enable Style/MissingRespondToMissing | |
35 | end | |
36 | end |
31 | 31 | formatter.output('@scan_aborted', |
32 | 32 | reason: e.is_a?(Interrupt) ? 'Canceled by User' : e.message, |
33 | 33 | trace: e.backtrace, |
34 | verbose: controllers.first.parsed_options[:verbose] || | |
34 | verbose: NS::ParsedCli.verbose || | |
35 | 35 | run_error_exit_code == NS::ExitCode::EXCEPTION) |
36 | 36 | ensure |
37 | 37 | formatter.beautify |
60 | 60 | at_exit do |
61 | 61 | exit(run_error_exit_code) if run_error |
62 | 62 | |
63 | controller = controllers.first | |
64 | ||
65 | 63 | # The parsed_option[:url] must be checked to avoid raising erros when only -h/-v are given |
66 | exit(NS::ExitCode::VULNERABLE) if controller.parsed_options[:url] && controller.target.vulnerable? | |
64 | exit(NS::ExitCode::VULNERABLE) if NS::ParsedCli.url && controllers.first.target.vulnerable? | |
67 | 65 | exit(NS::ExitCode::OK) |
68 | 66 | end |
69 | 67 | end |
19 | 19 | # which can be huge (~ 2Go) |
20 | 20 | res = head_and_get(path, [200], get: params.merge(headers: { 'Range' => 'bytes=0-700' })) |
21 | 21 | |
22 | res.body =~ pattern ? true : false | |
22 | res.body&.match?(pattern) ? true : false | |
23 | 23 | end |
24 | 24 | |
25 | 25 | # @param [ String ] path |
23 | 23 | require 'cms_scanner/numeric' # Adds a Numeric#bytes_to_human |
24 | 24 | # Custom Libs |
25 | 25 | require 'cms_scanner/scan' |
26 | require 'cms_scanner/parsed_cli' | |
26 | 27 | require 'cms_scanner/helper' |
27 | 28 | require 'cms_scanner/exit_code' |
28 | 29 | require 'cms_scanner/errors' |
0 | 0 | # frozen_string_literal: true |
1 | 1 | |
2 | 2 | describe CMSScanner::Controller::Core do |
3 | subject(:core) { described_class.new } | |
4 | let(:target_url) { 'http://example.com/' } | |
5 | let(:cli_args) { "--url #{target_url}" } | |
6 | let(:parsed_options) { rspec_parsed_options(cli_args) } | |
3 | subject(:core) { described_class.new } | |
4 | let(:target_url) { 'http://example.com/' } | |
5 | let(:cli_args) { "--url #{target_url}" } | |
7 | 6 | |
8 | 7 | before do |
9 | CMSScanner::Browser.reset | |
10 | described_class.parsed_options = parsed_options | |
8 | CMSScanner::ParsedCli.options = rspec_parsed_options(cli_args) | |
11 | 9 | end |
12 | 10 | |
13 | 11 | describe '#cli_options' do |
14 | 12 | its(:cli_options) { should_not be_empty } |
15 | 13 | its(:cli_options) { should be_a Array } |
16 | 14 | |
17 | it 'contaisn the expected options' do | |
15 | it 'contains the expected options' do | |
18 | 16 | expect(core.cli_options.map(&:to_sym)).to match_array( |
19 | 17 | %i[ |
20 | 18 | banner cache_dir cache_ttl clear_cache connect_timeout cookie_jar cookie_string |
36 | 34 | context 'when cache_dir' do |
37 | 35 | let(:cli_args) { "#{super()} --cache-dir #{CACHE}" } |
38 | 36 | let(:cache) { Typhoeus::Config.cache } |
39 | let(:storage) { File.join(parsed_options[:cache_dir], Digest::MD5.hexdigest(target_url)) } | |
37 | let(:storage) { File.join(CMSScanner::ParsedCli.cache_dir, Digest::MD5.hexdigest(target_url)) } | |
40 | 38 | |
41 | 39 | before { core.setup_cache } |
42 | 40 | after { Typhoeus::Config.cache = nil } |
260 | 258 | let(:cli_args) { "#{super()} --proxy-auth user:p@ss" } |
261 | 259 | |
262 | 260 | it 'raises an error' do |
263 | expect(CMSScanner::Browser.instance.proxy_auth).to eq(parsed_options[:proxy_auth]) | |
261 | expect(CMSScanner::Browser.instance.proxy_auth).to eq(CMSScanner::ParsedCli.proxy_auth) | |
264 | 262 | |
265 | 263 | expect { core.before_scan }.to raise_error(CMSScanner::Error::ProxyAuthRequired) |
266 | 264 | end |
272 | 270 | let(:cli_args) { "#{super()} --proxy-auth user:pass" } |
273 | 271 | |
274 | 272 | it 'raises an error' do |
275 | expect(CMSScanner::Browser.instance.proxy_auth).to eq(parsed_options[:proxy_auth]) | |
273 | expect(CMSScanner::Browser.instance.proxy_auth).to eq(CMSScanner::ParsedCli.proxy_auth) | |
276 | 274 | |
277 | 275 | expect { core.before_scan }.to_not raise_error |
278 | 276 | end |
3 | 3 | subject(:controller) { described_class.new } |
4 | 4 | let(:target_url) { 'http://example.com/' } |
5 | 5 | let(:cli_args) { "--url #{target_url}" } |
6 | let(:parsed_options) { rspec_parsed_options(cli_args) } | |
7 | 6 | |
8 | 7 | before do |
9 | CMSScanner::Browser.reset | |
10 | described_class.parsed_options = parsed_options | |
8 | CMSScanner::ParsedCli.options = rspec_parsed_options(cli_args) | |
11 | 9 | end |
12 | 10 | |
13 | 11 | its(:before_scan) { should be_nil } |
26 | 24 | before do |
27 | 25 | expect(controller.target).to receive(:interesting_findings) |
28 | 26 | .with( |
29 | mode: parsed_options[:interesting_findings_detection] || parsed_options[:detection_mode] | |
27 | mode: CMSScanner::ParsedCli.interesting_findings_detection || | |
28 | CMSScanner::ParsedCli.detection_mode | |
30 | 29 | ).and_return(stubbed) |
31 | 30 | end |
32 | 31 |
13 | 13 | let(:parsed_options) { { url: target_url, format: formatter.to_s.underscore.dasherize } } |
14 | 14 | |
15 | 15 | before do |
16 | controller.class.parsed_options = parsed_options | |
16 | CMSScanner::ParsedCli.options = parsed_options | |
17 | 17 | # Resets the formatter to ensure the correct one is loaded |
18 | 18 | controller.class.class_variable_set(:@@formatter, nil) |
19 | 19 | end |
80 | 80 | expected = case sym |
81 | 81 | when :user_agent |
82 | 82 | browser.default_user_agent |
83 | when :user_agents_list | |
84 | File.join(CMSScanner::APP_DIR, 'user_agents.txt') | |
85 | 83 | when :throttle |
86 | 84 | 0.0 |
87 | 85 | end |
218 | 216 | context 'when --random-user-agent' do |
219 | 217 | let(:options) { super().merge(random_user_agent: true) } |
220 | 218 | |
221 | it 'select a random UA in the user_agents' do | |
219 | it 'selects a random UA in the user_agents' do | |
220 | expect(browser).to receive(:user_agents_list).and_return(FIXTURES.join('user_agents.txt')) | |
221 | ||
222 | 222 | expect(browser.user_agent).to_not eql browser.default_user_agent |
223 | ||
223 | 224 | # Should not pick up a random one each time |
224 | 225 | expect(browser.user_agent).to eql browser.user_agent |
225 | 226 | end |
3 | 3 | let(:target_url) { 'http://wp.lab/' } |
4 | 4 | |
5 | 5 | before do |
6 | scanner = CMSScanner::Scan.new | |
7 | scanner.controllers.first.class.parsed_options = rspec_parsed_options("--url #{target_url}") | |
6 | CMSScanner::Scan.new # To initialize the start memory | |
7 | CMSScanner::ParsedCli.options = rspec_parsed_options("--url #{target_url}") | |
8 | 8 | end |
9 | 9 | |
10 | 10 | describe 'typhoeus memoize' do |
2 | 2 | describe CMSScanner::Controller do |
3 | 3 | subject(:controller) { described_class::Base.new } |
4 | 4 | |
5 | context 'when parsed_options' do | |
6 | before do | |
7 | described_class::Base.option_parser = nil | |
8 | described_class::Base.parsed_options = parsed_options | |
9 | end | |
5 | before do | |
6 | described_class::Base.option_parser = nil | |
10 | 7 | |
11 | let(:parsed_options) { { url: 'http://example.com/' } } | |
8 | CMSScanner::ParsedCli.options = parsed_options | |
9 | end | |
12 | 10 | |
13 | its(:option_parser) { should be nil } | |
14 | its(:parsed_options) { should eq(parsed_options) } | |
15 | its(:formatter) { should be_a CMSScanner::Formatter::Cli } | |
16 | its(:user_interaction?) { should be true } | |
17 | its(:tmp_directory) { should eql '/tmp/cms_scanner' } | |
18 | its(:target) { should be_a CMSScanner::Target } | |
19 | its('target.scope.domains') { should eq [PublicSuffix.parse('example.com')] } | |
11 | let(:parsed_options) { { url: 'http://example.com/' } } | |
20 | 12 | |
21 | context 'when output option' do | |
22 | let(:parsed_options) { super().merge(output: '/tmp/spec.txt') } | |
13 | its(:option_parser) { should be nil } | |
14 | its(:formatter) { should be_a CMSScanner::Formatter::Cli } | |
15 | its(:user_interaction?) { should be true } | |
16 | its(:tmp_directory) { should eql '/tmp/cms_scanner' } | |
17 | its(:target) { should be_a CMSScanner::Target } | |
18 | its('target.scope.domains') { should eq [PublicSuffix.parse('example.com')] } | |
23 | 19 | |
24 | its(:user_interaction?) { should be false } | |
25 | end | |
20 | context 'when output option' do | |
21 | let(:parsed_options) { super().merge(output: '/tmp/spec.txt') } | |
26 | 22 | |
27 | describe '#render' do | |
28 | it 'calls the formatter#render' do | |
29 | expect(controller.formatter).to receive(:render).with('test', { verbose: nil }, 'base') | |
30 | controller.render('test') | |
31 | end | |
23 | its(:user_interaction?) { should be false } | |
24 | end | |
25 | ||
26 | describe '#render' do | |
27 | it 'calls the formatter#render' do | |
28 | expect(controller.formatter).to receive(:render).with('test', { verbose: nil }, 'base') | |
29 | controller.render('test') | |
32 | 30 | end |
33 | 31 | end |
34 | 32 | end |
3 | 3 | module Controller |
4 | 4 | class Spec < Base |
5 | 5 | def before_scan |
6 | output('help', help: option_parser.simple_help, simple: true) if parsed_options[:help] | |
6 | output('help', help: option_parser.simple_help, simple: true) if NS::ParsedCli.help | |
7 | 7 | |
8 | exit(NS::ExitCode::OK) if parsed_options[:help] | |
8 | exit(NS::ExitCode::OK) if NS::ParsedCli.help | |
9 | 9 | end |
10 | 10 | end |
11 | 11 | |
57 | 57 | [base, spec].each { |c| expect(c).to receive(:before_scan).ordered } |
58 | 58 | [base, spec].each { |c| expect(c).to receive(:run).ordered } |
59 | 59 | |
60 | expect(hydra).to receive(:abort).ordered | |
60 | expect_any_instance_of(Typhoeus::Hydra).to receive(:abort) | |
61 | 61 | |
62 | 62 | [spec, base].each { |c| expect(c).to receive(:after_scan).ordered } |
63 | 63 | |
79 | 79 | .ordered |
80 | 80 | .with('help', hash_including(:help, :simple), 'spec') |
81 | 81 | |
82 | expect(hydra).to receive(:abort).ordered | |
82 | expect_any_instance_of(Typhoeus::Hydra).to receive(:abort) | |
83 | 83 | |
84 | 84 | expect { controllers.run }.to raise_error(SystemExit) |
85 | 85 | end |
97 | 97 | let(:max_scan_duration) { 1 } |
98 | 98 | |
99 | 99 | it 'raises an exception' do |
100 | expect(hydra).to receive(:abort).ordered | |
100 | expect_any_instance_of(Typhoeus::Hydra).to receive(:abort) | |
101 | 101 | |
102 | 102 | controllers.reverse_each { |c| expect(c).to receive(:after_scan).ordered } |
103 | 103 |
0 | # frozen_string_literal: true | |
1 | ||
2 | describe CMSScanner::ParsedCli do | |
3 | subject(:parsed_cli) { described_class } | |
4 | let(:options) { { key: 'value', cache_ttl: 10 } } | |
5 | ||
6 | describe '#options=' do | |
7 | it 'sets them, reset the Browser and pass them to it' do | |
8 | expect(CMSScanner::Browser.instance.cache_ttl).to eql nil # Not yet set | |
9 | ||
10 | parsed_cli.options = options | |
11 | expect(CMSScanner::Browser.instance.cache_ttl).to eql 10 | |
12 | end | |
13 | ||
14 | context 'when the options are modified from the top after being passed' do | |
15 | it 'does not modify them' do | |
16 | parsed_cli.options = options | |
17 | ||
18 | options[:key3] = 'value3' | |
19 | ||
20 | expect(parsed_cli.options).to eql(key: 'value', cache_ttl: 10) | |
21 | end | |
22 | end | |
23 | ||
24 | context 'when passing nil' do | |
25 | it 'sets an empty hash' do | |
26 | parsed_cli.options = nil | |
27 | ||
28 | expect(parsed_cli.options).to eql({}) | |
29 | end | |
30 | end | |
31 | end | |
32 | ||
33 | describe '.options, .verbose etc' do | |
34 | it 'has the correct values' do | |
35 | parsed_cli.options = options | |
36 | ||
37 | expect(parsed_cli.options).to eql options | |
38 | ||
39 | expect(parsed_cli.verbose?).to be false | |
40 | ||
41 | expect(parsed_cli.key).to eql 'value' | |
42 | expect(parsed_cli.cache_ttl).to eql 10 | |
43 | ||
44 | expect(parsed_cli.nothing).to eql nil | |
45 | end | |
46 | end | |
47 | end |
56 | 56 | let(:target_url) { 'http://ex.lo/' } |
57 | 57 | |
58 | 58 | before do |
59 | scanner.controllers.first.class.parsed_options = { url: target_url } | |
59 | SubScanner::ParsedCli.options = { url: target_url } | |
60 | 60 | end |
61 | 61 | |
62 | 62 | describe '#app_name' do |
1 | 1 | |
2 | 2 | shared_examples CMSScanner::Target::Platform::PHP do |
3 | 3 | before do |
4 | if path =~ /\.log\z/i | |
4 | if /\.log\z/i.match?(path) | |
5 | 5 | expect(target).to receive(:head_or_get_params).and_return(method: :head) |
6 | 6 | |
7 | 7 | stub_request(:head, target.url(path)).and_return(status: head_status) |
15 | 15 | RSpec.configure do |config| |
16 | 16 | config.expect_with :rspec do |c| |
17 | 17 | c.syntax = :expect |
18 | end | |
19 | ||
20 | config.before(:each) do | |
21 | # Needed for rspec to run w/o error due to the at_exit hook calling the controller#target | |
22 | CMSScanner::Controller::Core.parsed_options = { url: 'http://ex.lo' } | |
23 | 18 | end |
24 | 19 | end |
25 | 20 |