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.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/.rubocop.yml +6 -0
- data/.travis.yml +14 -0
- data/Gemfile +6 -0
- data/README.md +20 -0
- data/Rakefile +9 -0
- data/app/app.rb +4 -0
- data/app/controllers.rb +2 -0
- data/app/controllers/core.rb +46 -0
- data/app/controllers/core/cli_options.rb +68 -0
- data/app/controllers/interesting_files.rb +12 -0
- data/app/finders.rb +1 -0
- data/app/finders/interesting_files.rb +21 -0
- data/app/finders/interesting_files/fantastico_fileslist.rb +23 -0
- data/app/finders/interesting_files/headers.rb +15 -0
- data/app/finders/interesting_files/robots_txt.rb +22 -0
- data/app/finders/interesting_files/search_replace_db_2.rb +28 -0
- data/app/finders/interesting_files/xml_rpc.rb +62 -0
- data/app/formatters.rb +3 -0
- data/app/formatters/cli.rb +18 -0
- data/app/formatters/cli_no_colour.rb +15 -0
- data/app/formatters/json.rb +12 -0
- data/app/models.rb +5 -0
- data/app/models/fantastico_fileslist.rb +20 -0
- data/app/models/headers.rb +37 -0
- data/app/models/interesting_file.rb +30 -0
- data/app/models/robots_txt.rb +20 -0
- data/app/models/xml_rpc.rb +35 -0
- data/app/views/cli/core/finished.erb +4 -0
- data/app/views/cli/core/started.erb +3 -0
- data/app/views/cli/interesting_files/findings.erb +19 -0
- data/app/views/cli/scan_aborted.erb +4 -0
- data/app/views/json/core/finished.erb +3 -0
- data/app/views/json/core/started.erb +3 -0
- data/app/views/json/interesting_files/findings.erb +1 -0
- data/app/views/json/scan_aborted.erb +4 -0
- data/cms_scanner.gemspec +37 -0
- data/examples/views/cli/wp_custom/test.erb +1 -0
- data/examples/views/json/wp_custom/test.erb +1 -0
- data/examples/wpscan.rb +29 -0
- data/lib/cms_scanner.rb +71 -0
- data/lib/cms_scanner/browser.rb +68 -0
- data/lib/cms_scanner/browser/actions.rb +48 -0
- data/lib/cms_scanner/browser/options.rb +53 -0
- data/lib/cms_scanner/cache/file_store.rb +75 -0
- data/lib/cms_scanner/cache/typhoeus.rb +21 -0
- data/lib/cms_scanner/controller.rb +90 -0
- data/lib/cms_scanner/controllers.rb +34 -0
- data/lib/cms_scanner/errors/auth_errors.rb +15 -0
- data/lib/cms_scanner/finders.rb +5 -0
- data/lib/cms_scanner/finders/finder.rb +27 -0
- data/lib/cms_scanner/finders/finding.rb +32 -0
- data/lib/cms_scanner/finders/findings.rb +25 -0
- data/lib/cms_scanner/finders/independent_finder.rb +30 -0
- data/lib/cms_scanner/finders/independent_finders.rb +41 -0
- data/lib/cms_scanner/formatter.rb +118 -0
- data/lib/cms_scanner/formatter/buffer.rb +15 -0
- data/lib/cms_scanner/target.rb +33 -0
- data/lib/cms_scanner/target/platform.rb +2 -0
- data/lib/cms_scanner/target/platform/php.rb +39 -0
- data/lib/cms_scanner/target/platform/wordpress.rb +35 -0
- data/lib/cms_scanner/target/platform/wordpress/custom_directories.rb +62 -0
- data/lib/cms_scanner/target/server.rb +3 -0
- data/lib/cms_scanner/target/server/apache.rb +43 -0
- data/lib/cms_scanner/target/server/generic.rb +34 -0
- data/lib/cms_scanner/target/server/iis.rb +48 -0
- data/lib/cms_scanner/version.rb +4 -0
- data/lib/cms_scanner/web_site.rb +68 -0
- data/lib/helper.rb +24 -0
- data/spec/app/controllers/core_spec.rb +152 -0
- data/spec/app/controllers/interesting_files_spec.rb +50 -0
- data/spec/app/finders/interesting_files/fantastico_fileslist_spec.rb +68 -0
- data/spec/app/finders/interesting_files/headers_spec.rb +38 -0
- data/spec/app/finders/interesting_files/robots_txt_spec.rb +56 -0
- data/spec/app/finders/interesting_files/search_replace_db_2_spec.rb +55 -0
- data/spec/app/finders/interesting_files/xml_rpc_spec.rb +138 -0
- data/spec/app/finders/interesting_files_spec.rb +13 -0
- data/spec/app/formatters/cli_no_colour_spec.rb +17 -0
- data/spec/app/formatters/cli_spec.rb +21 -0
- data/spec/app/formatters/json_spec.rb +33 -0
- data/spec/app/models/fantastico_fileslist_spec.rb +32 -0
- data/spec/app/models/headers_spec.rb +52 -0
- data/spec/app/models/interesting_file_spec.rb +51 -0
- data/spec/app/models/robots_txt_spec.rb +28 -0
- data/spec/app/models/xml_rpc_spec.rb +47 -0
- data/spec/cache/.gitignore +4 -0
- data/spec/dummy_finders.rb +41 -0
- data/spec/fixtures/interesting_files/fantastico_fileslist/fantastico_fileslist.txt +12 -0
- data/spec/fixtures/interesting_files/file.txt +4 -0
- data/spec/fixtures/interesting_files/headers/interesting.txt +14 -0
- data/spec/fixtures/interesting_files/headers/no_interesting.txt +12 -0
- data/spec/fixtures/interesting_files/robots_txt/robots.txt +10 -0
- data/spec/fixtures/interesting_files/search_replace_db_2/searchreplacedb2.php +188 -0
- data/spec/fixtures/interesting_files/xml_rpc/homepage_in_scope_pingback.html +7 -0
- data/spec/fixtures/interesting_files/xml_rpc/homepage_out_of_scope_pingback.html +7 -0
- data/spec/fixtures/interesting_files/xml_rpc/xmlrpc.php +1 -0
- data/spec/fixtures/output.txt +0 -0
- data/spec/fixtures/target/platform/php/debug_log/debug.log +2 -0
- data/spec/fixtures/target/platform/php/fpd/wp_rss_functions.php +2 -0
- data/spec/fixtures/target/platform/wordpress/custom_directories/custom_w_spaces.html +10 -0
- data/spec/fixtures/target/platform/wordpress/custom_directories/default.html +14 -0
- data/spec/fixtures/target/platform/wordpress/custom_directories/https.html +12 -0
- data/spec/fixtures/target/platform/wordpress/detection/default.html +4 -0
- data/spec/fixtures/target/platform/wordpress/detection/not_wp.html +8 -0
- data/spec/fixtures/target/platform/wordpress/detection/wp_includes.html +3 -0
- data/spec/fixtures/target/server/apache/directory_listing/2.2.16.html +15 -0
- data/spec/fixtures/target/server/generic/server/apache/basic.txt +5 -0
- data/spec/fixtures/target/server/generic/server/iis/basic.txt +6 -0
- data/spec/fixtures/target/server/generic/server/not_detected.txt +3 -0
- data/spec/fixtures/target/server/iis/directory_listing/no_parent.html +3 -0
- data/spec/fixtures/target/server/iis/directory_listing/with_parent.html +3 -0
- data/spec/fixtures/views/base/ctrl/local.erb +1 -0
- data/spec/fixtures/views/base/ctrl/test.erb +3 -0
- data/spec/fixtures/views/base/global.erb +1 -0
- data/spec/fixtures/views/base/test.erb +2 -0
- data/spec/fixtures/views/based_format/test.erb +1 -0
- data/spec/fixtures/views/json/render_me.erb +4 -0
- data/spec/lib/browser_spec.rb +141 -0
- data/spec/lib/cache/file_store_spec.rb +101 -0
- data/spec/lib/cache/typhoeus_spec.rb +30 -0
- data/spec/lib/cms_scanner_spec.rb +45 -0
- data/spec/lib/controller_spec.rb +23 -0
- data/spec/lib/controllers_spec.rb +52 -0
- data/spec/lib/finders/findings_spec.rb +49 -0
- data/spec/lib/finders/independent_finders_spec.rb +98 -0
- data/spec/lib/formatter_spec.rb +136 -0
- data/spec/lib/sub_scanner_spec.rb +27 -0
- data/spec/lib/target/platforms_spec.rb +13 -0
- data/spec/lib/target/servers_spec.rb +13 -0
- data/spec/lib/target_spec.rb +50 -0
- data/spec/lib/web_site_spec.rb +124 -0
- data/spec/shared_examples.rb +11 -0
- data/spec/shared_examples/browser_actions.rb +32 -0
- data/spec/shared_examples/finding.rb +20 -0
- data/spec/shared_examples/formatter_buffer.rb +8 -0
- data/spec/shared_examples/formatter_class_methods.rb +26 -0
- data/spec/shared_examples/independent_finder.rb +33 -0
- data/spec/shared_examples/target/platform/php.rb +58 -0
- data/spec/shared_examples/target/platform/wordpress.rb +41 -0
- data/spec/shared_examples/target/platform/wordpress/custom_directories.rb +50 -0
- data/spec/shared_examples/target/server/apache.rb +33 -0
- data/spec/shared_examples/target/server/generic.rb +34 -0
- data/spec/shared_examples/target/server/iis.rb +38 -0
- data/spec/spec_helper.rb +41 -0
- metadata +432 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
module CMSScanner
|
|
2
|
+
# WebSite Implementation
|
|
3
|
+
class WebSite
|
|
4
|
+
attr_reader :uri
|
|
5
|
+
|
|
6
|
+
def initialize(site_url)
|
|
7
|
+
self.url = site_url.dup
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def url=(site_url)
|
|
11
|
+
# Add a trailing slash to the site url
|
|
12
|
+
site_url << '/' if site_url[-1, 1] != '/'
|
|
13
|
+
|
|
14
|
+
# Use the validator to ensure the site_url has a correct format
|
|
15
|
+
OptParseValidator::OptURL.new([]).validate(site_url)
|
|
16
|
+
|
|
17
|
+
@uri = Addressable::URI.parse(site_url)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Used for convenience
|
|
21
|
+
#
|
|
22
|
+
# @param [ String ] path Optional path to merge with the uri
|
|
23
|
+
#
|
|
24
|
+
# @return [ String ]
|
|
25
|
+
def url(path = nil)
|
|
26
|
+
@uri.join(path || '').to_s
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Checks if the remote website is up.
|
|
30
|
+
#
|
|
31
|
+
# @return [ Boolean ]
|
|
32
|
+
def online?
|
|
33
|
+
NS::Browser.get(url).code != 0
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @return [ Boolean ]
|
|
37
|
+
def http_auth?
|
|
38
|
+
NS::Browser.get(url).code == 401
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @return [ Boolean ]
|
|
42
|
+
def proxy_auth?
|
|
43
|
+
NS::Browser.get(url).code == 407
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# See if the remote url returns 30x redirect
|
|
47
|
+
# This method is recursive
|
|
48
|
+
#
|
|
49
|
+
# @param [ String ] url
|
|
50
|
+
#
|
|
51
|
+
# @return [ String ] The redirection url or nil
|
|
52
|
+
def redirection(url = nil)
|
|
53
|
+
url ||= @uri.to_s
|
|
54
|
+
response = NS::Browser.get(url)
|
|
55
|
+
|
|
56
|
+
if response.code == 301 || response.code == 302
|
|
57
|
+
redirection = response.headers_hash['location']
|
|
58
|
+
|
|
59
|
+
# Let's check if there is a redirection in the redirection
|
|
60
|
+
if (other_redirection = redirection(redirection))
|
|
61
|
+
redirection = other_redirection
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
redirection
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
data/lib/helper.rb
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
|
|
2
|
+
# @param [ String ] file The file path
|
|
3
|
+
def redirect_output_to_file(file)
|
|
4
|
+
$stdout.reopen(file, 'w')
|
|
5
|
+
$stdout.sync = true
|
|
6
|
+
$stderr.reopen($stdout) # Not sure if this is needed
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# @return [ Integer ] The memory of the current process in Bytes
|
|
10
|
+
def memory_usage
|
|
11
|
+
`ps -o rss= -p #{Process.pid}`.to_i * 1024 # ps returns the value in KB
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Hack of the Numeric class
|
|
15
|
+
class Numeric
|
|
16
|
+
# @return [ String ] A human readable string of the value
|
|
17
|
+
def bytes_to_human
|
|
18
|
+
units = %w(B KB MB GB TB)
|
|
19
|
+
e = self > 0 ? (Math.log(self) / Math.log(1024)).floor : 0
|
|
20
|
+
s = format('%.3f', (to_f / 1024**e))
|
|
21
|
+
|
|
22
|
+
s.sub(/\.?0*$/, ' ' + units[e])
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe CMSScanner::Controller::Core do
|
|
4
|
+
|
|
5
|
+
subject(:core) { described_class.new }
|
|
6
|
+
let(:target_url) { 'http://example.com/' }
|
|
7
|
+
let(:parsed_options) { { url: target_url } }
|
|
8
|
+
|
|
9
|
+
before do
|
|
10
|
+
CMSScanner::Browser.reset
|
|
11
|
+
described_class.parsed_options = parsed_options
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
its(:cli_options) { should_not be_empty }
|
|
15
|
+
its(:cli_options) { should be_a Array }
|
|
16
|
+
|
|
17
|
+
describe '#setup_cache' do
|
|
18
|
+
context 'when no cache_dir supplied (or default)' do
|
|
19
|
+
it 'returns nil' do
|
|
20
|
+
expect(core.setup_cache).to eq nil
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
context 'when cache_dir' do
|
|
25
|
+
let(:parsed_options) { super().merge(cache_dir: CACHE) }
|
|
26
|
+
let(:cache) { Typhoeus::Config.cache }
|
|
27
|
+
let(:storage) { File.join(parsed_options[:cache_dir], Digest::MD5.hexdigest(target_url)) }
|
|
28
|
+
|
|
29
|
+
before { core.setup_cache }
|
|
30
|
+
after { Typhoeus::Config.cache = nil }
|
|
31
|
+
|
|
32
|
+
it 'sets up the cache' do
|
|
33
|
+
expect(cache).to be_a CMSScanner::Cache::Typhoeus
|
|
34
|
+
expect(cache.storage_path).to eq storage
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
describe '#before_scan' do
|
|
40
|
+
it 'raise an error when the site is down' do
|
|
41
|
+
stub_request(:get, target_url).to_return(status: 0)
|
|
42
|
+
|
|
43
|
+
expect { core.before_scan }
|
|
44
|
+
.to raise_error("The url supplied '#{target_url}' seems to be down")
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it 'raises an error when the site redirects' do
|
|
48
|
+
redirection = 'http://somewhere.com'
|
|
49
|
+
|
|
50
|
+
stub_request(:get, target_url).to_return(status: 301, headers: { location: redirection })
|
|
51
|
+
stub_request(:get, redirection).to_return(status: 200)
|
|
52
|
+
|
|
53
|
+
expect { core.before_scan }
|
|
54
|
+
.to raise_error("The url supplied redirects to #{redirection}")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# This is quite a mess (as Webmock doesn't issue itself another 401
|
|
58
|
+
# when credential are incorrect :/)
|
|
59
|
+
context 'when http authentication' do
|
|
60
|
+
context 'when no credentials' do
|
|
61
|
+
before { stub_request(:get, target_url).to_return(status: 401) }
|
|
62
|
+
|
|
63
|
+
it 'raises an error' do
|
|
64
|
+
expect { core.before_scan }.to raise_error(CMSScanner::HTTPAuthRequiredError)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
context 'when credentials' do
|
|
69
|
+
context 'when valid' do
|
|
70
|
+
before { stub_request(:get, 'http://user:pass@example.com') }
|
|
71
|
+
|
|
72
|
+
let(:parsed_options) { super().merge(http_auth: { username: 'user', password: 'pass' }) }
|
|
73
|
+
|
|
74
|
+
it 'does not raise any error' do
|
|
75
|
+
expect { core.before_scan }.to_not raise_error
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
context 'when invalid' do
|
|
80
|
+
before { stub_request(:get, 'http://user:p@ss@example.com').to_return(status: 401) }
|
|
81
|
+
|
|
82
|
+
let(:parsed_options) { super().merge(http_auth: { username: 'user', password: 'p@ss' }) }
|
|
83
|
+
|
|
84
|
+
it 'raises an error' do
|
|
85
|
+
expect { core.before_scan }.to raise_error(CMSScanner::HTTPAuthRequiredError)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
context 'when proxy authentication' do
|
|
92
|
+
before { stub_request(:get, target_url).to_return(status: 407) }
|
|
93
|
+
|
|
94
|
+
context 'when no credentials' do
|
|
95
|
+
it 'raises an error' do
|
|
96
|
+
expect { core.before_scan }.to raise_error(CMSScanner::ProxyAuthRequiredError)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
context 'when invalid credentials' do
|
|
101
|
+
let(:parsed_options) { super().merge(proxy_auth: { username: 'user', password: 'p@ss' }) }
|
|
102
|
+
|
|
103
|
+
it 'raises an error' do
|
|
104
|
+
expect(CMSScanner::Browser.instance.proxy_auth).to eq(parsed_options[:proxy_auth])
|
|
105
|
+
|
|
106
|
+
expect { core.before_scan }.to raise_error(CMSScanner::ProxyAuthRequiredError)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
context 'when valid credentials' do
|
|
111
|
+
before { stub_request(:get, target_url) }
|
|
112
|
+
|
|
113
|
+
let(:parsed_options) { super().merge(proxy_auth: { username: 'user', password: 'pass' }) }
|
|
114
|
+
|
|
115
|
+
it 'raises an error' do
|
|
116
|
+
expect(CMSScanner::Browser.instance.proxy_auth).to eq(parsed_options[:proxy_auth])
|
|
117
|
+
|
|
118
|
+
expect { core.before_scan }.to_not raise_error
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
describe '#run' do
|
|
125
|
+
it 'calls the formatter with the correct parameters' do
|
|
126
|
+
expect(core.formatter).to receive(:output)
|
|
127
|
+
.with('started',
|
|
128
|
+
hash_including(:start_memory, :start_time, :verbose, url: target_url),
|
|
129
|
+
'core')
|
|
130
|
+
|
|
131
|
+
core.run
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
describe '#after_scan' do
|
|
136
|
+
let(:keys) { [:verbose, :start_time, :stop_time, :start_memory, :elapsed, :used_memory] }
|
|
137
|
+
|
|
138
|
+
it 'calles the formatter with the correct parameters' do
|
|
139
|
+
# Call the #run once to ensure that @start_time & @start_memory are set
|
|
140
|
+
expect(core).to receive(:output).with('started', url: target_url)
|
|
141
|
+
core.run
|
|
142
|
+
|
|
143
|
+
RSpec::Mocks.space.proxy_for(core).reset # Must reset due to the above statements
|
|
144
|
+
|
|
145
|
+
expect(core.formatter).to receive(:output)
|
|
146
|
+
.with('finished', hash_including(*keys), 'core')
|
|
147
|
+
|
|
148
|
+
core.after_scan
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe CMSScanner::Controller::InterestingFiles do
|
|
4
|
+
|
|
5
|
+
subject(:controller) { described_class.new }
|
|
6
|
+
let(:target_url) { 'http://example.com/' }
|
|
7
|
+
let(:parsed_options) { { url: target_url } }
|
|
8
|
+
|
|
9
|
+
before do
|
|
10
|
+
CMSScanner::Browser.reset
|
|
11
|
+
described_class.parsed_options = parsed_options
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
its(:cli_options) { should be_nil }
|
|
15
|
+
its(:before_scan) { should be_nil }
|
|
16
|
+
its(:after_scan) { should be_nil }
|
|
17
|
+
|
|
18
|
+
describe '#run' do
|
|
19
|
+
before do
|
|
20
|
+
expect(controller.target).to receive(:interesting_files)
|
|
21
|
+
.with(mode: parsed_options[:detection_mode]).and_return(stubbed)
|
|
22
|
+
end
|
|
23
|
+
after { controller.run }
|
|
24
|
+
|
|
25
|
+
[:mixed, :passive, :aggressive].each do |mode|
|
|
26
|
+
context "when #{mode} mode" do
|
|
27
|
+
let(:parsed_options) { super().merge(detection_mode: mode) }
|
|
28
|
+
|
|
29
|
+
context 'when no findings' do
|
|
30
|
+
let(:stubbed) { [] }
|
|
31
|
+
|
|
32
|
+
it 'does not call the formatter' do
|
|
33
|
+
expect(controller.formatter).to_not receive(:output)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# TODO: Test the output with a dummy finding ?
|
|
38
|
+
context 'when findings' do
|
|
39
|
+
let(:stubbed) { ['yolo'] }
|
|
40
|
+
|
|
41
|
+
it 'calls the formatter with the correct parameter' do
|
|
42
|
+
expect(controller.formatter).to receive(:output)
|
|
43
|
+
.with('findings', hash_including(findings: stubbed), 'interesting_files')
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe CMSScanner::Finders::InterestingFile::FantasticoFileslist do
|
|
4
|
+
|
|
5
|
+
subject(:finder) { described_class.new(target) }
|
|
6
|
+
let(:target) { CMSScanner::Target.new(url) }
|
|
7
|
+
let(:url) { 'http://example.com/' }
|
|
8
|
+
let(:file) { url + 'fantastico_fileslist.txt' }
|
|
9
|
+
let(:fixtures) { File.join(FIXTURES, 'interesting_files', 'fantastico_fileslist') }
|
|
10
|
+
|
|
11
|
+
describe '#url' do
|
|
12
|
+
its(:url) { should eq file }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
describe '#aggressive' do
|
|
16
|
+
after do
|
|
17
|
+
stub_request(:get, file).to_return(status: status, body: body, headers: headers)
|
|
18
|
+
|
|
19
|
+
result = finder.aggressive
|
|
20
|
+
|
|
21
|
+
expect(result).to be_a CMSScanner::FantasticoFileslist if @expected
|
|
22
|
+
expect(result).to eql @expected
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
let(:body) { '' }
|
|
26
|
+
let(:headers) { { 'Content-Type' => 'text/html ' } }
|
|
27
|
+
|
|
28
|
+
context 'when 404' do
|
|
29
|
+
let(:status) { 404 }
|
|
30
|
+
|
|
31
|
+
it 'returns nil' do
|
|
32
|
+
@expected = nil
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
context 'when 200' do
|
|
37
|
+
let(:status) { 200 }
|
|
38
|
+
|
|
39
|
+
context 'when the body is empty' do
|
|
40
|
+
it 'returns nil' do
|
|
41
|
+
@expected = nil
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
context 'when not a text/plain Content-Type' do
|
|
46
|
+
let(:body) { 'not an empty body' }
|
|
47
|
+
|
|
48
|
+
it 'returns nil' do
|
|
49
|
+
@expected = nil
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
context 'when the body matches and Content-Type = text/plain' do
|
|
54
|
+
let(:body) { File.new(File.join(fixtures, 'fantastico_fileslist.txt')).read }
|
|
55
|
+
let(:headers) { { 'Content-Type' => 'text/plain' } }
|
|
56
|
+
|
|
57
|
+
it 'returns the InterestingFile result' do
|
|
58
|
+
@expected = CMSScanner::FantasticoFileslist.new(
|
|
59
|
+
file,
|
|
60
|
+
confidence: 100,
|
|
61
|
+
found_by: 'FantasticoFileslist (aggressive detection)'
|
|
62
|
+
)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe CMSScanner::Finders::InterestingFile::Headers do
|
|
4
|
+
|
|
5
|
+
subject(:finder) { described_class.new(target) }
|
|
6
|
+
let(:target) { CMSScanner::Target.new(url) }
|
|
7
|
+
let(:url) { 'http://example.com/' }
|
|
8
|
+
let(:fixtures) { File.join(FIXTURES, 'interesting_files', 'headers') }
|
|
9
|
+
let(:fixture) { File.join(fixtures, 'interesting.txt') }
|
|
10
|
+
let(:headers) { parse_headers_file(fixture) }
|
|
11
|
+
|
|
12
|
+
describe '#passive' do
|
|
13
|
+
before { stub_request(:get, url).to_return(headers: headers) }
|
|
14
|
+
|
|
15
|
+
after do
|
|
16
|
+
if @expected
|
|
17
|
+
result = finder.passive
|
|
18
|
+
|
|
19
|
+
expect(result).to be_a CMSScanner::Headers
|
|
20
|
+
expect(result).to eql @expected
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
context 'when no headers' do
|
|
25
|
+
let(:headers) { {} }
|
|
26
|
+
|
|
27
|
+
its(:passive) { should be nil }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
context 'when headers' do
|
|
31
|
+
it 'returns the result' do
|
|
32
|
+
opts = { confidence: 100, found_by: 'Headers (passive detection)' }
|
|
33
|
+
@expected = CMSScanner::Headers.new(url, opts)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe CMSScanner::Finders::InterestingFile::RobotsTxt do
|
|
4
|
+
|
|
5
|
+
subject(:finder) { described_class.new(target) }
|
|
6
|
+
let(:target) { CMSScanner::Target.new(url) }
|
|
7
|
+
let(:url) { 'http://example.com/' }
|
|
8
|
+
let(:robots_txt) { url + 'robots.txt' }
|
|
9
|
+
let(:fixtures) { File.join(FIXTURES, 'interesting_files', 'robots_txt') }
|
|
10
|
+
|
|
11
|
+
describe '#url' do
|
|
12
|
+
its(:url) { should eq robots_txt }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
describe '#aggressive' do
|
|
16
|
+
after do
|
|
17
|
+
stub_request(:get, robots_txt).to_return(status: status, body: body)
|
|
18
|
+
|
|
19
|
+
result = finder.aggressive
|
|
20
|
+
|
|
21
|
+
expect(result).to be_a CMSScanner::RobotsTxt if @expected
|
|
22
|
+
expect(finder.aggressive).to eql @expected
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
let(:body) { '' }
|
|
26
|
+
|
|
27
|
+
context 'when 404' do
|
|
28
|
+
let(:status) { 404 }
|
|
29
|
+
|
|
30
|
+
it 'returns nil' do
|
|
31
|
+
@expected = nil
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
context 'when 200' do
|
|
36
|
+
let(:status) { 200 }
|
|
37
|
+
|
|
38
|
+
context 'when the body is empty' do
|
|
39
|
+
it 'returns nil' do
|
|
40
|
+
@expected = nil
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
context 'when the body matches a robots.txt' do
|
|
45
|
+
let(:body) { File.new(File.join(fixtures, 'robots.txt')).read }
|
|
46
|
+
|
|
47
|
+
it 'returns the InterestingFile result' do
|
|
48
|
+
@expected = CMSScanner::RobotsTxt.new(robots_txt,
|
|
49
|
+
confidence: 100,
|
|
50
|
+
found_by: 'RobotsTxt (aggressive detection)')
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
end
|