HELLO v3!!!

This commit is contained in:
Ryan Dewhurst
2018-09-26 21:12:01 +02:00
parent 28b9c15256
commit d268a86795
1871 changed files with 988118 additions and 0 deletions

View File

@@ -0,0 +1,38 @@
require 'spec_helper'
describe WPScan::Controller::Aliases do
subject(:controller) { described_class.new }
let(:target_url) { 'http://ex.lo/' }
let(:parsed_options) { rspec_parsed_options(cli_args) }
let(:cli_args) { "--url #{target_url}" }
before do
WPScan::Browser.reset
described_class.parsed_options = parsed_options
end
describe '#cli_options' do
its(:cli_options) { should_not be_empty }
its(:cli_options) { should be_a Array }
it 'contains to correct options' do
expect(controller.cli_options.map(&:to_sym)).to eq %i[stealthy]
end
end
describe 'parsed_options' do
context 'when no --stealthy supplied' do
its(:parsed_options) { should eql parsed_options }
end
context 'when --stealthy supplied' do
let(:cli_args) { "#{super()} --stealthy" }
it 'contains the correct options' do
expect(controller.parsed_options).to include(
random_user_agent: true, detection_mode: :passive, plugins_version_detection: :passive
)
end
end
end
end

View File

@@ -0,0 +1,271 @@
require 'spec_helper'
describe WPScan::Controller::Core do
subject(:core) { described_class.new }
let(:target_url) { 'http://ex.lo/' }
let(:parsed_options) { rspec_parsed_options(cli_args) }
let(:cli_args) { "--url #{target_url}" }
before do
WPScan::Browser.reset
described_class.reset
described_class.parsed_options = parsed_options
end
describe '#cli_options' do
its(:cli_options) { should_not be_empty }
its(:cli_options) { should be_a Array }
it 'contains to correct options' do
cli_options = core.cli_options
expect(cli_options.map(&:to_sym)).to include(:url, :server, :force, :update)
# Ensures the :url is the first one and is correctly setup
expect(cli_options.first.to_sym).to eql :url
expect(cli_options.first.required_unless).to match_array %i[update help version]
end
end
describe '#load_server_module' do
after do
expect(core.target).to receive(:server).and_return(@stubbed_server)
expect(core.load_server_module).to eql @expected
[core.target, WPScan::WpItem.new(target_url, core.target)].each do |instance|
expect(instance).to respond_to(:directory_listing?)
expect(instance).to respond_to(:directory_listing_entries)
# The below doesn't work, the module would have to be removed from the class
# TODO: find a way to test this
# expect(instance.server).to eql @expected if instance.is_a? WPScan::WpItem
end
end
context 'when no --server supplied' do
%i[Apache IIS Nginx].each do |server|
it "loads the #{server} module and returns :#{server}" do
@stubbed_server = server
@expected = server
end
end
end
context 'when --server' do
%i[apache iis nginx].each do |server|
context "when #{server}" do
let(:cli_args) { "#{super()} --server #{server}" }
it "loads the #{server.capitalize} module and returns :#{server}" do
@stubbed_server = [:Apache, nil, :IIS, :Nginx].sample
@expected = server == :iis ? :IIS : server.to_s.camelize.to_sym
end
end
end
end
end
describe '#update_db_required?' do
context 'when missing files' do
before { expect(core.local_db).to receive(:missing_files?).ordered.and_return(true) }
context 'when --no-update' do
let(:cli_args) { "#{super()} --no-update" }
it 'raises an error' do
expect { core.update_db_required? }. to raise_error(WPScan::MissingDatabaseFile)
end
end
context 'otherwise' do
its(:update_db_required?) { should eql true }
end
end
context 'when not missing files' do
before { expect(core.local_db).to receive(:missing_files?).ordered.and_return(false) }
context 'when --update' do
let(:cli_args) { "#{super()} --update" }
its(:update_db_required?) { should eql true }
end
context 'when --no-update' do
let(:cli_args) { "#{super()} --no-update" }
its(:update_db_required?) { should eql false }
end
context 'when user_interation (i.e cli output)' do
let(:cli_args) { "#{super()} --format cli" }
context 'when the db is up-to-date' do
before { expect(core.local_db).to receive(:outdated?).and_return(false) }
its(:update_db_required?) { should eql false }
end
context 'when the db is outdated' do
before do
expect(core.local_db).to receive(:outdated?).ordered.and_return(true)
expect(core.formatter).to receive(:output).with('@notice', hash_including(:msg), 'core').ordered
expect($stdout).to receive(:write).ordered # for the print()
end
context 'when a positive answer' do
before { expect(Readline).to receive(:readline).and_return('Yes').ordered }
its(:update_db_required?) { should eql true }
end
context 'when a negative answer' do
before { expect(Readline).to receive(:readline).and_return('no').ordered }
its(:update_db_required?) { should eql false }
end
end
end
context 'when no user_interation' do
let(:cli_args) { "#{super()} --format json" }
its(:update_db_required?) { should eql false }
end
end
end
describe '#before_scan' do
before do
stub_request(:get, target_url)
expect(core.formatter).to receive(:output).with('banner', hash_including(verbose: nil), 'core')
expect(core).to receive(:update_db_required?).and_return(false) unless parsed_options[:update]
end
context 'when --update' do
before do
expect(core.formatter).to receive(:output)
.with('db_update_started', hash_including(verbose: nil), 'core').ordered
expect_any_instance_of(WPScan::DB::Updater).to receive(:update)
expect(core.formatter).to receive(:output)
.with('db_update_finished', hash_including(verbose: nil), 'core').ordered
end
context 'when the --url is not supplied' do
let(:cli_args) { '--update' }
it 'calls the formatter when started and finished to update the db and exit' do
expect { core.before_scan }.to raise_error(SystemExit)
end
end
context 'when --url is supplied' do
let(:cli_args) { "#{super()} --update" }
before do
expect(core).to receive(:load_server_module)
expect(core.target).to receive(:wordpress?).and_return(true)
end
it 'calls the formatter when started and finished to update the db' do
expect { core.before_scan }.to_not raise_error
end
end
end
context 'when a redirect occurs' do
before do
stub_request(:any, target_url)
expect(core.target).to receive(:homepage_res)
.at_least(1)
.and_return(Typhoeus::Response.new(effective_url: redirection, body: ''))
end
context 'to the wp-admin/install.php' do
let(:redirection) { "#{target_url}wp-admin/install.php" }
it 'calls the formatter with the correct parameters and exit' do
expect(core.formatter).to receive(:output)
.with('not_fully_configured', hash_including(url: redirection), 'core').ordered
# TODO: Would be cool to be able to test the exit code
expect { core.before_scan }.to raise_error(SystemExit)
end
end
context 'to something else' do
let(:redirection) { 'http://g.com/' }
it 'raises an error' do
expect { core.before_scan }.to raise_error(CMSScanner::HTTPRedirectError)
end
end
context 'to another path with the wp-admin/install.php in the query' do
let(:redirection) { "#{target_url}index.php?a=/wp-admin/install.php" }
context 'when wordpress' do
it 'does not raise an error' do
expect(core.target).to receive(:wordpress?).and_return(true)
expect { core.before_scan }.to_not raise_error
end
end
context 'when not wordpress' do
it 'raises an error' do
expect(core.target).to receive(:wordpress?).and_return(false)
expect { core.before_scan }.to raise_error(WPScan::NotWordPressError)
end
end
end
end
context 'when hosted on wordpress.com' do
let(:target_url) { 'http://ex.wordpress.com' }
before { expect(core).to receive(:load_server_module) }
it 'raises an error' do
expect { core.before_scan }.to raise_error(WPScan::WordPressHostedError)
end
end
context 'when wordpress' do
before do
expect(core).to receive(:load_server_module)
expect(core.target).to receive(:wordpress?).and_return(true)
end
it 'does not raise any error' do
expect { core.before_scan }.to_not raise_error
end
end
context 'when not wordpress' do
before do
expect(core).to receive(:load_server_module)
expect(core.target).to receive(:wordpress?).and_return(false)
end
context 'when no --force' do
it 'raises an error' do
expect { core.before_scan }.to raise_error(WPScan::NotWordPressError)
end
end
context 'when --force' do
let(:cli_args) { "#{super()} --force" }
it 'does not raise any error' do
expect { core.before_scan }.to_not raise_error
end
end
end
end
end

View File

@@ -0,0 +1,45 @@
require 'spec_helper'
describe WPScan::Controller::CustomDirectories do
subject(:controller) { described_class.new }
let(:target_url) { 'http://ex.lo/' }
let(:parsed_options) { rspec_parsed_options(cli_args) }
let(:cli_args) { "--url #{target_url}" }
before do
WPScan::Browser.reset
described_class.parsed_options = parsed_options
end
describe '#cli_options' do
its(:cli_options) { should_not be_empty }
its(:cli_options) { should be_a Array }
it 'contains to correct options' do
expect(controller.cli_options.map(&:to_sym)).to eq %i[wp_content_dir wp_plugins_dir]
end
end
describe '#before_scan' do
context 'when the content_dir is not found and not supply' do
before { expect(controller.target).to receive(:content_dir) }
let(:exception) do
'Unable to identify the wp-content dir, please supply it with --wp-content-dir'
end
it 'raises an exception' do
expect { controller.before_scan }.to raise_error(exception)
end
end
context 'when content_dir found/supplied' do
let(:cli_args) { "#{super()} --wp-content-dir wp-content" }
it 'does not raise any error' do
expect { controller.before_scan }.to_not raise_error
expect(controller.target.content_dir).to eq parsed_options[:wp_content_dir]
end
end
end
end

View File

@@ -0,0 +1,180 @@
require 'spec_helper'
describe WPScan::Controller::Enumeration do
subject(:controller) { described_class.new }
let(:target_url) { 'http://wp.lab/' }
let(:parsed_options) { rspec_parsed_options(cli_args) }
let(:cli_args) { "--url #{target_url}" }
before do
WPScan::Browser.reset
## For the --passwords options
allow_any_instance_of(OptParseValidator::OptPath).to receive(:check_file)
described_class.parsed_options = parsed_options
end
describe '#enum_message' do
after { expect(controller.enum_message(type)).to eql @expected }
context 'when type argument is incorrect' do
let(:type) { 'spec' }
it 'returns nil' do
@expected = nil
end
end
%w[plugins themes].each do |t|
context "type = #{t}" do
let(:type) { t }
context 'when vulnerable' do
let(:cli_args) { "#{super()} -e v#{type[0]}" }
it 'returns the expected string' do
@expected = "Enumerating Vulnerable #{type.capitalize}"
end
end
context 'when all' do
let(:cli_args) { "#{super()} -e a#{type[0]}" }
it 'returns the expected string' do
@expected = "Enumerating All #{type.capitalize}"
end
end
context 'when most popular' do
let(:cli_args) { "#{super()} -e #{type[0]}" }
it 'returns the expected string' do
@expected = "Enumerating Most Popular #{type.capitalize}"
end
end
end
end
end
describe '#default_opts' do
context 'when no --enumerate' do
it 'contains the correct version_detection' do
expect(controller.default_opts('plugins')[:version_detection]).to include(mode: :mixed)
end
end
end
describe '#cli_options' do
it 'contains the correct options' do
expect(controller.cli_options.map(&:to_sym)).to eql(
%i[enumerate exclude_content_based
plugins_list plugins_detection plugins_version_all plugins_version_detection
themes_list themes_detection themes_version_all themes_version_detection
timthumbs_list timthumbs_detection
config_backups_list config_backups_detection
db_exports_list db_exports_detection
medias_detection
users_list users_detection]
)
end
end
describe '#enum_users' do
before { expect(controller.formatter).to receive(:output).twice }
after { controller.enum_users }
context 'when --enumerate has been supplied' do
let(:cli_args) { "#{super()} -e u1-10" }
it 'calls the target.users with the correct range' do
expect(controller.target).to receive(:users).with(hash_including(range: (1..10)))
end
end
context 'when --passwords supplied but no --username or --usernames' do
let(:cli_args) { "#{super()} --passwords some-file.txt" }
it 'calls the target.users with the default range' do
expect(controller.target).to receive(:users).with(hash_including(range: (1..10)))
end
end
end
describe '#before_scan' do
it 'creates the Dynamic Finders' do
expect(WPScan::DB::DynamicFinders::Plugin).to receive(:create_versions_finders)
expect(WPScan::DB::DynamicFinders::Theme).to receive(:create_versions_finders)
controller.before_scan
end
end
describe '#run' do
context 'when no :enumerate' do
before do
expect(controller).to receive(:enum_plugins)
expect(controller).to receive(:enum_config_backups)
expect(parsed_options[:plugins_detection]).to eql :passive
end
it 'calls enum_plugins and enum_config_backups' do
controller.run
end
context 'when --passwords supplied but no --username or --usernames' do
let(:cli_args) { "#{super()} --passwords some-file.txt" }
it 'calls the enum_users' do
expect(controller).to receive(:enum_users)
controller.run
end
end
end
context 'when :enumerate' do
after { controller.run }
context 'when no option supplied' do
let(:cli_args) { "#{super()} -e" }
it 'calls the correct enum methods' do
%i[plugins themes timthumbs config_backups db_exports users medias].each do |option|
expect(controller).to receive("enum_#{option}".to_sym)
end
end
end
%i[p ap vp].each do |option|
context "when #{option}" do
let(:cli_args) { "#{super()} -e #{option}" }
it 'calls the #enum_plugins' do
expect(controller).to receive(:enum_plugins)
end
end
end
%i[t at vt].each do |option|
context option.to_s do
let(:cli_args) { "#{super()} -e #{option}" }
it 'calls the #enum_themes' do
expect(controller).to receive(:enum_themes)
end
end
end
{ timthumbs: 'tt', config_backups: 'cb', db_exports: 'dbe', medias: 'm', users: 'u' }.each do |option, shortname|
context "when #{option}" do
let(:cli_args) { "#{super()} -e #{shortname}" }
it "calls the ##{option}" do
expect(controller).to receive("enum_#{option}".to_sym)
end
end
end
end
end
end

View File

@@ -0,0 +1,169 @@
require 'spec_helper'
describe WPScan::Controller::PasswordAttack do
subject(:controller) { described_class.new }
let(:target_url) { 'http://ex.lo/' }
let(:parsed_options) { rspec_parsed_options(cli_args) }
let(:cli_args) { "--url #{target_url}" }
before do
WPScan::Browser.reset
described_class.parsed_options = parsed_options
end
describe '#cli_options' do
its(:cli_options) { should_not be_empty }
its(:cli_options) { should be_a Array }
it 'contains to correct options' do
expect(controller.cli_options.map(&:to_sym))
.to eq(%i[passwords usernames multicall_max_passwords password_attack])
end
end
describe '#users' do
context 'when no --usernames' do
it 'calles target.users' do
expect(controller.target).to receive(:users)
controller.users
end
end
context 'when --usernames' do
let(:cli_args) { "#{super()} --usernames admin,editor" }
it 'returns an array with the users' do
expected = %w[admin editor].reduce([]) do |a, e|
a << CMSScanner::User.new(e)
end
expect(controller.users).to eql expected
end
end
end
describe '#passwords' do
xit
end
describe '#run' do
context 'when no --passwords is supplied' do
it 'does not run the attacker' do
expect(controller.run).to eql nil
end
end
end
describe '#attacker' do
context 'when --password-attack provided' do
let(:cli_args) { "#{super()} --password-attack #{attack}" }
context 'when wp-login' do
let(:attack) { 'wp-login' }
it 'returns the correct object' do
expect(controller.attacker).to be_a WPScan::Finders::Passwords::WpLogin
expect(controller.attacker.target).to be_a WPScan::Target
end
end
context 'when xmlrpc' do
before do
expect(controller.target).to receive(:xmlrpc).and_return(WPScan::XMLRPC.new("#{target_url}/xmlrpc.php"))
end
context 'when single xmlrpc' do
let(:attack) { 'xmlrpc' }
it 'returns the correct object' do
expect(controller.attacker).to be_a WPScan::Finders::Passwords::XMLRPC
expect(controller.attacker.target).to be_a WPScan::XMLRPC
end
end
context 'when xmlrpc-multicall' do
let(:attack) { 'xmlrpc-multicall' }
it 'returns the correct object' do
expect(controller.attacker).to be_a WPScan::Finders::Passwords::XMLRPCMulticall
expect(controller.attacker.target).to be_a WPScan::XMLRPC
end
end
end
end
context 'when automatic detection' do
before { expect(controller.target).to receive(:xmlrpc).and_return(xmlrpc) }
context 'when xmlrpc not found' do
let(:xmlrpc) { nil }
it 'returns the WpLogin' do
expect(controller.attacker).to be_a WPScan::Finders::Passwords::WpLogin
expect(controller.attacker.target).to be_a WPScan::Target
end
end
context 'when xmlrpc not enabled' do
let(:xmlrpc) { WPScan::XMLRPC.new("#{target_url}/xmlrpc.php") }
it 'returns the WpLogin' do
expect(xmlrpc).to receive(:enabled?).and_return(false)
expect(controller.attacker).to be_a WPScan::Finders::Passwords::WpLogin
expect(controller.attacker.target).to be_a WPScan::Target
end
end
context 'when xmlrpc enabled' do
let(:xmlrpc) { WPScan::XMLRPC.new("#{target_url}/xmlrpc.php") }
before { expect(xmlrpc).to receive(:enabled?).and_return(true) }
context 'when wp.getUsersBlogs methods not available' do
it 'returns the WpLogin' do
expect(xmlrpc).to receive(:available_methods).and_return(%w[m1 m2])
expect(controller.attacker).to be_a WPScan::Finders::Passwords::WpLogin
expect(controller.attacker.target).to be_a WPScan::Target
end
end
context 'when wp.getUsersBlogs method evailable' do
before { expect(xmlrpc).to receive(:available_methods).and_return(%w[wp.getUsersBlogs m2]) }
context 'when WP version not found' do
it 'returns the XMLRPC' do
expect(controller.target).to receive(:wp_version).and_return(false)
expect(controller.attacker).to be_a WPScan::Finders::Passwords::XMLRPC
expect(controller.attacker.target).to be_a WPScan::XMLRPC
end
end
context 'when WP version found' do
before { expect(controller.target).to receive(:wp_version).and_return(wp_version) }
context 'when WP < 4.4' do
let(:wp_version) { WPScan::WpVersion.new('3.8.1') }
it 'returns the XMLRPCMulticall' do
expect(controller.attacker).to be_a WPScan::Finders::Passwords::XMLRPCMulticall
expect(controller.attacker.target).to be_a WPScan::XMLRPC
end
end
context 'when WP >= 4.4' do
let(:wp_version) { WPScan::WpVersion.new('4.4') }
it 'returns the XMLRPC' do
expect(controller.attacker).to be_a WPScan::Finders::Passwords::XMLRPC
expect(controller.attacker.target).to be_a WPScan::XMLRPC
end
end
end
end
end
end
end
end

View File

@@ -0,0 +1,85 @@
require 'spec_helper'
def it_calls_the_formatter_with_the_correct_parameter(version)
it 'calls the formatter with the correct parameter' do
expect(controller.formatter).to receive(:output)
.with('version', hash_including(version: version), 'wp_version')
end
end
describe WPScan::Finders::WpVersionFinders do
subject(:finders) { described_class.new }
describe 'filter_findings' do
context 'when super returns false (nothing found)' do
before do
expect_any_instance_of(WPScan::Finders::UniqueFinders).to receive(:filter_findings).and_return(false)
end
its(:filter_findings) { should be false }
end
end
end
describe WPScan::Controller::WpVersion do
subject(:controller) { described_class.new }
let(:target_url) { 'http://ex.lo/' }
let(:parsed_options) { rspec_parsed_options(cli_args) }
let(:cli_args) { "--url #{target_url}" }
before do
WPScan::Browser.reset
described_class.parsed_options = parsed_options
end
describe '#cli_options' do
its(:cli_options) { should_not be_empty }
its(:cli_options) { should be_a Array }
it 'contains to correct options' do
expect(controller.cli_options.map(&:to_sym)).to eq %i[wp_version_all wp_version_detection]
end
end
describe '#run' do
before do
expect(controller.target).to receive(:wp_version)
.with(
hash_including(
mode: parsed_options[:wp_version_detection] || parsed_options[:detection_mode],
confidence_threshold: parsed_options[:wp_version_all] ? 0 : 100
)
).and_return(stubbed)
end
after { controller.run }
%i[mixed passive aggressive].each do |mode|
context "when --detection-mode #{mode}" do
let(:cli_args) { "#{super()} --detection-mode #{mode}" }
[WPScan::WpVersion.new('4.0')].each do |version|
context "when version = #{version}" do
let(:stubbed) { version }
it_calls_the_formatter_with_the_correct_parameter(version)
end
end
end
end
context 'when --wp-version-all supplied' do
let(:cli_args) { "#{super()} --wp-version-all" }
let(:stubbed) { WPScan::WpVersion.new('3.9.1') }
it_calls_the_formatter_with_the_correct_parameter(WPScan::WpVersion.new('3.9.1'))
end
context 'when --wp-version-detection mode supplied' do
let(:cli_args) { "#{super()} --detection-mode mixed --wp-version-detection passive" }
let(:stubbed) { WPScan::WpVersion.new('4.4') }
it_calls_the_formatter_with_the_correct_parameter(WPScan::WpVersion.new('4.4'))
end
end
end