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

View File

@@ -0,0 +1,52 @@
require 'spec_helper'
describe WPScan::Finders::ConfigBackups::KnownFilenames do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url) }
let(:url) { 'http://ex.lo/' }
let(:fixtures) { File.join(FINDERS_FIXTURES, 'config_backups') }
let(:opts) { { list: File.join(WPScan::DB_DIR, 'config_backups.txt') } }
describe '#aggressive' do
before do
expect(target).to receive(:sub_dir).at_least(1).and_return(false)
expect(target).to receive(:homepage_or_404?).at_least(1).and_return(false)
finder.potential_urls(opts).each_key do |url|
stub_request(:get, url).to_return(status: 404)
end
end
context 'when all files are 404s' do
it 'returns an empty array' do
expect(finder.aggressive(opts)).to eql []
end
end
context 'when some files exist' do
let(:files) { ['%23wp-config.php%23', 'wp-config.bak'] }
let(:config_backup) { File.read(File.join(fixtures, 'wp-config.php')) }
before do
files.each do |file|
stub_request(:get, "#{url}#{file}").to_return(body: config_backup)
end
end
it 'returns the expected Array<ConfigBackup>' do
expected = []
files.each do |file|
url = "#{target.url}#{file}"
expected << WPScan::ConfigBackup.new(
url,
confidence: 100,
found_by: described_class::DIRECT_ACCESS
)
end
expect(finder.aggressive(opts)).to eql expected
end
end
end
end

View File

@@ -0,0 +1,13 @@
require 'spec_helper'
describe WPScan::Finders::ConfigBackups::Base do
subject(:config_backups) { described_class.new(target) }
let(:target) { WPScan::Target.new(url) }
let(:url) { 'http://ex.lo/' }
describe '#finders' do
it 'contains the expected finders' do
expect(config_backups.finders.map { |f| f.class.to_s.demodulize }).to eq %w[KnownFilenames]
end
end
end

View File

@@ -0,0 +1,69 @@
require 'spec_helper'
describe WPScan::Finders::DbExports::KnownLocations do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url) }
let(:url) { 'http://ex.lo/aa/' }
let(:fixtures) { File.join(FINDERS_FIXTURES, 'db_exports') }
let(:opts) { { list: File.join(WPScan::DB_DIR, 'db_exports.txt') } }
describe '#potential_urls' do
before do
expect(target).to receive(:sub_dir).at_least(1).and_return(false)
end
it 'replace {domain_name} by its value' do
expect(finder.potential_urls(opts).keys).to eql %w[
http://ex.lo/aa/ex.sql
http://ex.lo/aa/wordpress.sql
http://ex.lo/aa/backup/ex.zip
http://ex.lo/aa/backup/mysql.sql
http://ex.lo/aa/backups/ex.sql.gz
http://ex.lo/aa/backups/db_backup.sql
]
end
end
describe '#aggressive' do
before do
expect(target).to receive(:sub_dir).at_least(1).and_return(false)
expect(target).to receive(:homepage_or_404?).at_least(1).and_return(false)
finder.potential_urls(opts).each_key do |url|
stub_request(:get, url).to_return(status: 404)
end
end
context 'when all files are 404s' do
it 'returns an empty array' do
expect(finder.aggressive(opts)).to eql []
end
end
context 'when some files exist' do
let(:files) { %w[ex.sql backups/db_backup.sql] }
let(:db_export) { File.read(File.join(fixtures, 'dump.sql')) }
before do
files.each do |file|
stub_request(:get, "#{url}#{file}").to_return(body: db_export)
end
end
it 'returns the expected Array<DbExport>' do
expected = []
files.each do |file|
url = "#{target.url}#{file}"
expected << WPScan::DbExport.new(
url,
confidence: 100,
found_by: described_class::DIRECT_ACCESS
)
end
expect(finder.aggressive(opts)).to eql expected
end
end
end
end

View File

@@ -0,0 +1,13 @@
require 'spec_helper'
describe WPScan::Finders::DbExports::Base do
subject(:db_exports) { described_class.new(target) }
let(:target) { WPScan::Target.new(url) }
let(:url) { 'http://ex.lo/' }
describe '#finders' do
it 'contains the expected finders' do
expect(db_exports.finders.map { |f| f.class.to_s.demodulize }).to eq %w[KnownLocations]
end
end
end

View File

@@ -0,0 +1,64 @@
require 'spec_helper'
describe WPScan::Finders::InterestingFindings::BackupDB do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url).extend(CMSScanner::Target::Server::Apache) }
let(:url) { 'http://ex.lo/' }
let(:fixtures) { File.join(FINDERS_FIXTURES, 'interesting_findings', 'backup_db') }
let(:wp_content) { 'wp-content' }
let(:dir_url) { target.url("#{wp_content}/backup-db/") }
before { expect(target).to receive(:content_dir).at_least(1).and_return(wp_content) }
describe '#aggressive' do
before { stub_request(:get, dir_url).to_return(status: status, body: body) }
let(:body) { '' }
context 'when not a 200 or 403' do
let(:status) { 404 }
its(:aggressive) { should be_nil }
end
context 'when 200 and matching the homepage' do
before { expect(target).to receive(:homepage_or_404?).and_return(true) }
let(:status) { 200 }
its(:aggressive) { should be_nil }
end
context 'when 200 or 403' do
before { expect(target).to receive(:homepage_or_404?).and_return(false) }
let(:status) { 200 }
after do
found = finder.aggressive
expect(found).to eql WPScan::InterestingFinding.new(
dir_url,
confidence: 70,
found_by: described_class::DIRECT_ACCESS
)
expect(found.interesting_entries).to eq @expected_entries
end
context 'when no directory listing' do
it 'returns an empty interesting_findings attribute' do
@expected_entries = []
end
end
context 'when directory listing enabled' do
let(:body) { File.read(File.join(fixtures, 'dir_listing.html')) }
it 'returns the expected interesting_findings attribute' do
@expected_entries = %w[sqldump.sql test.txt]
end
end
end
end
end

View File

@@ -0,0 +1,34 @@
require 'spec_helper'
describe WPScan::Finders::InterestingFindings::DebugLog do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url) }
let(:url) { 'http://ex.lo/' }
let(:fixtures) { File.join(FINDERS_FIXTURES, 'interesting_findings', 'debug_log') }
let(:wp_content) { 'wp-content' }
let(:log_url) { target.url("#{wp_content}/debug.log") }
before { expect(target).to receive(:content_dir).at_least(1).and_return(wp_content) }
describe '#aggressive' do
before { stub_request(:get, log_url).to_return(body: body) }
context 'when empty file' do
let(:body) { '' }
its(:aggressive) { should be_nil }
end
context 'when a log file' do
let(:body) { File.read(File.join(fixtures, 'debug.log')) }
it 'returns the InterestingFinding' do
expect(finder.aggressive).to eql WPScan::InterestingFinding.new(
log_url,
confidence: 100,
found_by: described_class::DIRECT_ACCESS
)
end
end
end
end

View File

@@ -0,0 +1,35 @@
require 'spec_helper'
describe WPScan::Finders::InterestingFindings::DuplicatorInstallerLog do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url).extend(CMSScanner::Target::Server::Apache) }
let(:url) { 'http://ex.lo/' }
let(:fixtures) { File.join(FINDERS_FIXTURES, 'interesting_findings', 'duplicator_installer_log') }
let(:filename) { 'installer-log.txt' }
let(:log_url) { target.url(filename) }
describe '#aggressive' do
before do
expect(target).to receive(:sub_dir).at_least(1).and_return(false)
stub_request(:get, log_url).to_return(body: body)
end
context 'when the body does not match' do
let(:body) { '' }
its(:aggressive) { should be_nil }
end
context 'when the body matches' do
let(:body) { File.read(File.join(fixtures, filename)) }
it 'returns the InterestingFinding' do
expect(finder.aggressive).to eql WPScan::InterestingFinding.new(
log_url,
confidence: 100,
found_by: described_class::DIRECT_ACCESS
)
end
end
end
end

View File

@@ -0,0 +1,12 @@
require 'spec_helper'
describe WPScan::Finders::InterestingFindings::EmergencyPwdResetScript do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url).extend(CMSScanner::Target::Server::Apache) }
let(:url) { 'http://ex.lo/' }
let(:fixtures) { File.join(FINDERS_FIXTURES, 'interesting_findings', 'emergency_pwd_reset_script') }
describe '#aggressive' do
xit
end
end

View File

@@ -0,0 +1,37 @@
require 'spec_helper'
describe WPScan::Finders::InterestingFindings::FullPathDisclosure do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url) }
let(:url) { 'http://ex.lo/' }
let(:fixtures) { File.join(FINDERS_FIXTURES, 'interesting_findings', 'fpd') }
let(:file_url) { target.url('wp-includes/rss-functions.php') }
describe '#aggressive' do
before do
expect(target).to receive(:sub_dir).at_least(1).and_return(false)
stub_request(:get, file_url).to_return(body: body)
end
context 'when empty file' do
let(:body) { '' }
its(:aggressive) { should be_nil }
end
context 'when a log file' do
let(:body) { File.read(File.join(fixtures, 'rss_functions.php')) }
it 'returns the InterestingFinding' do
found = finder.aggressive
expect(found).to eql WPScan::InterestingFinding.new(
file_url,
confidence: 100,
found_by: described_class::DIRECT_ACCESS
)
expect(found.interesting_entries).to eql %w[/blog/wp-includes/rss-functions.php]
end
end
end
end

View File

@@ -0,0 +1,16 @@
require 'spec_helper'
describe WPScan::Finders::InterestingFindings::MuPlugins do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url).extend(CMSScanner::Target::Server::Apache) }
let(:url) { 'http://ex.lo/' }
let(:fixtures) { File.join(FINDERS_FIXTURES, 'interesting_findings', 'mu_plugins') }
describe '#passive' do
xit
end
describe '#aggressive' do
xit
end
end

View File

@@ -0,0 +1,12 @@
require 'spec_helper'
describe WPScan::Finders::InterestingFindings::Multisite do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url).extend(CMSScanner::Target::Server::Apache) }
let(:url) { 'http://ex.lo/' }
let(:fixtures) { File.join(FINDERS_FIXTURES, 'interesting_findings', 'multisite') }
describe '#aggressive' do
xit
end
end

View File

@@ -0,0 +1,46 @@
require 'spec_helper'
describe WPScan::Finders::InterestingFindings::Readme do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url) }
let(:url) { 'http://ex.lo/' }
let(:fixtures) { File.join(FINDERS_FIXTURES, 'interesting_findings', 'readme') }
describe '#aggressive' do
before do
expect(target).to receive(:sub_dir).at_least(1).and_return(false)
finder.potential_files.each do |file|
stub_request(:get, target.url(file)).to_return(status: 404)
end
end
context 'when no file present' do
its(:aggressive) { should be_nil }
end
# TODO: case when multiple files are present ? (should return only the first one found)
context 'when a file exists' do
let(:file) { finder.potential_files.sample }
let(:readme) { File.read(File.join(fixtures, 'readme-3.9.2.html')) }
before { stub_request(:get, target.url(file)).to_return(body: readme) }
it 'returns the expected InterestingFinding' do
expected = WPScan::InterestingFinding.new(
target.url(file),
confidence: 100,
found_by: described_class::DIRECT_ACCESS
)
expect(finder.aggressive).to eql expected
end
end
end
describe '#potential_files' do
it 'does not contain duplicates' do
expect(finder.potential_files.flatten.uniq.length).to eql finder.potential_files.length
end
end
end

View File

@@ -0,0 +1,12 @@
require 'spec_helper'
describe WPScan::Finders::InterestingFindings::Registration do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url).extend(CMSScanner::Target::Server::Apache) }
let(:url) { 'http://ex.lo/' }
let(:fixtures) { File.join(FINDERS_FIXTURES, 'interesting_findings', 'registration') }
describe '#aggressive' do
xit
end
end

View File

@@ -0,0 +1,12 @@
require 'spec_helper'
describe WPScan::Finders::InterestingFindings::TmmDbMigrate do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url).extend(CMSScanner::Target::Server::Apache) }
let(:url) { 'http://ex.lo/' }
let(:fixtures) { File.join(FINDERS_FIXTURES, 'interesting_findings', 'tmm_db_migrate') }
describe '#aggressive' do
xit
end
end

View File

@@ -0,0 +1,13 @@
require 'spec_helper'
describe WPScan::Finders::InterestingFindings::UploadDirectoryListing do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url).extend(CMSScanner::Target::Server::Apache) }
let(:url) { 'http://ex.lo/' }
let(:fixtures) { File.join(FINDERS_FIXTURES, 'interesting_findings', 'upload_directory_listing') }
let(:wp_content) { 'wp-content' }
describe '#aggressive' do
xit
end
end

View File

@@ -0,0 +1,50 @@
require 'spec_helper'
describe WPScan::Finders::InterestingFindings::UploadSQLDump do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url).extend(CMSScanner::Target::Server::Apache) }
let(:url) { 'http://ex.lo/' }
let(:fixtures) { File.join(FINDERS_FIXTURES, 'interesting_findings', 'upload_sql_dump') }
let(:wp_content) { 'wp-content' }
describe '#aggressive' do
before { expect(target).to receive(:content_dir).at_least(1).and_return(wp_content) }
after { expect(finder.aggressive).to eql @expected }
context 'when not a 200' do
it 'returns nil' do
stub_request(:get, finder.dump_url).to_return(status: 404)
@expected = nil
end
end
context 'when a 200' do
before do
stub_request(:get, finder.dump_url)
.to_return(status: 200, body: File.read(File.join(fixtures, fixture)))
end
context 'when the body does not match a SQL dump' do
let(:fixture) { 'not_sql.txt' }
it 'returns nil' do
@expected = nil
end
end
context 'when the body matches a SQL dump' do
let(:fixture) { 'dump.sql' }
it 'returns the interesting findings' do
@expected = WPScan::InterestingFinding.new(
finder.dump_url,
confidence: 100,
found_by: described_class::DIRECT_ACCESS
)
end
end
end
end
end

View File

@@ -0,0 +1,21 @@
require 'spec_helper'
describe WPScan::Finders::InterestingFindings::Base do
subject(:files) { described_class.new(target) }
let(:target) { WPScan::Target.new(url) }
let(:url) { 'http://ex.lo/' }
describe '#finders' do
let(:expected) do
%w[
Readme DebugLog FullPathDisclosure
Multisite MuPlugins Registration UploadDirectoryListing TmmDbMigrate
UploadSQLDump
]
end
it 'contains the expected finders' do
expect(files.finders.map { |f| f.class.to_s.demodulize }).to include(*expected)
end
end
end

View File

@@ -0,0 +1,58 @@
require 'spec_helper'
describe WPScan::Finders::MainTheme::CssStyle do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url).extend(CMSScanner::Target::Server::Apache) }
let(:url) { 'http://wp.lab/' }
let(:fixtures) { File.join(FINDERS_FIXTURES, 'main_theme', 'css_style') }
describe '#passive' do
after do
stub_request(:get, url).to_return(body: File.read(File.join(fixtures, fixture)))
expect(finder.passive).to eql @expected
end
context 'when no in scope style' do
let(:fixture) { 'no_in_scope_style.html' }
it 'returns nil' do
@expected = nil
end
end
context 'when in scope style' do
before do
expect(target).to receive(:content_dir).at_least(1).and_return('wp-content')
stub_request(:get, /.*.css/)
end
context 'when in a link href' do
let(:fixture) { 'link_href.html' }
it 'returns the expected theme' do
@expected = WPScan::Theme.new(
'twentyfifteen',
target,
found_by: 'Css Style (Passive Detection)',
confidence: 70,
style_url: 'http://wp.lab/wp-content/themes/twentyfifteen/style.css?ver=4.1.1'
)
end
end
context 'when in the style code' do
let(:fixture) { 'style_code.html' }
it 'returns the expected theme' do
@expected = WPScan::Theme.new(
'custom',
target,
found_by: 'Css Style (Passive Detection)',
confidence: 70,
style_url: 'http://wp.lab/wp-content/themes/custom/style.css'
)
end
end
end
end
end

View File

@@ -0,0 +1,35 @@
require 'spec_helper'
describe WPScan::Finders::MainTheme::UrlsInHomepage do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url) }
let(:url) { 'http://wp.lab/' }
let(:fixtures) { File.join(FINDERS_FIXTURES, 'main_theme', 'urls_in_homepage') }
it_behaves_like 'App::Finders::WpItems::URLsInHomepage' do
let(:type) { 'themes' }
let(:uniq_links) { false }
let(:uniq_codes) { false }
let(:expected_from_links) { %w[twentyfifteen twentyfifteen twentyfifteen yolo] }
let(:expected_from_codes) { %w[test yolo] }
end
describe '#passive' do
before do
stub_request(:get, /.*.css/)
stub_request(:get, target.url).to_return(body: File.read(File.join(fixtures, 'found.html')))
end
it 'returns the expected Themes' do
@expected = []
{ 'twentyfifteen' => 6, 'yolo' => 4, 'test' => 2 }.each do |slug, confidence|
@expected << WPScan::Theme.new(
slug, target, found_by: 'Urls In Homepage (Passive Detection)', confidence: confidence
)
end
expect(finder.passive).to eql @expected
end
end
end

View File

@@ -0,0 +1,39 @@
require 'spec_helper'
describe WPScan::Finders::MainTheme::WooFrameworkMetaGenerator do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url).extend(CMSScanner::Target::Server::Apache) }
let(:url) { 'http://wp.lab/' }
let(:fixtures) { File.join(FINDERS_FIXTURES, 'main_theme', 'woo_framework_meta_generator') }
describe '#passive' do
after do
stub_request(:get, url).to_return(body: File.read(File.join(fixtures, @file)))
expect(finder.passive).to eql @expected
end
context 'when no Woo generator' do
it 'returns nil' do
@file = 'no_woo_generator.html'
@expected = nil
end
end
context 'when Woo generator' do
before do
expect(target).to receive(:content_dir).at_least(1).and_return('wp-content')
stub_request(:get, "#{url}wp-content/themes/Merchant/style.css")
end
it 'returns the expected theme' do
@file = 'woo_generator.html'
@expected = WPScan::Theme.new(
'Merchant', target,
found_by: 'Woo Framework Meta Generator (Passive Detection)',
confidence: 80
)
end
end
end
end

View File

@@ -0,0 +1,14 @@
require 'spec_helper'
describe WPScan::Finders::MainTheme::Base do
subject(:main_theme) { described_class.new(target) }
let(:target) { WPScan::Target.new(url) }
let(:url) { 'http://ex.lo/' }
describe '#finders' do
it 'contains the expected finders' do
expect(main_theme.finders.map { |f| f.class.to_s.demodulize })
.to eq %w[CssStyle WooFrameworkMetaGenerator UrlsInHomepage]
end
end
end

View File

@@ -0,0 +1,21 @@
require 'spec_helper'
describe WPScan::Finders::Medias::AttachmentBruteForcing do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url) }
let(:url) { 'http://ex.lo/' }
let(:fixtures) { File.join(FINDERS_FIXTURES, 'medias', 'attachment_brute_forcing') }
describe '#aggressive' do
xit
end
describe '#target_urls' do
it 'returns the expected urls' do
expect(finder.target_urls(range: (1..2))).to eql(
url + '?attachment_id=1' => 1,
url + '?attachment_id=2' => 2
)
end
end
end

View File

@@ -0,0 +1,13 @@
require 'spec_helper'
describe WPScan::Finders::Medias::Base do
subject(:media) { described_class.new(target) }
let(:target) { WPScan::Target.new(url) }
let(:url) { 'http://ex.lo/' }
describe '#finders' do
it 'contains the expected finders' do
expect(media.finders.map { |f| f.class.to_s.demodulize }).to eq %w[AttachmentBruteForcing]
end
end
end

View File

@@ -0,0 +1,116 @@
require 'spec_helper'
describe WPScan::Finders::PluginVersion::Readme do
subject(:finder) { described_class.new(plugin) }
let(:plugin) { WPScan::Plugin.new('spec', target) }
let(:target) { WPScan::Target.new('http://wp.lab/') }
let(:fixtures) { File.join(FINDERS_FIXTURES, 'plugin_version', 'readme') }
def version(number, found_by, confidence)
WPScan::Version.new(
number,
found_by: format('Readme - %s (Aggressive Detection)', found_by),
confidence: confidence,
interesting_entries: [readme_url]
)
end
def stable_tag(number)
version(number, 'Stable Tag', 80)
end
def changelog_section(number)
version(number, 'ChangeLog Section', 50)
end
describe '#aggressive' do
before { expect(target).to receive(:content_dir).and_return('wp-content') }
after do
stub_request(:get, /.*/).to_return(status: 404)
stub_request(:get, readme_url).to_return(body: File.read(File.join(fixtures, @file)))
expect(finder.aggressive).to eql @expected
end
let(:readme_url) { plugin.url(WPScan::WpItem::READMES.sample) }
context 'when no version' do
it 'returns nil' do
@file = 'no_version.txt'
@expected = nil
end
end
context 'when the stable tag does not contain numbers' do
it 'returns nil' do
@file = 'aa-health-calculator.txt'
@expected = nil
end
end
context 'when empty changelog section' do
it 'returns nil' do
@file = 'all-in-one-facebook.txt'
@expected = nil
end
end
context 'when no changelog section' do
it 'returns nil' do
@file = 'blog-reordering.txt'
@expected = nil
end
end
context 'when leaked from the stable tag' do
it 'returns the expected versions' do
@file = 'simple-login-lockdown-0.4.txt'
@expected = [stable_tag('0.4'), changelog_section('04')]
end
end
context 'when leaked from the version' do
it 'returns it' do
@file = 'wp-photo-plus-5.1.15.txt'
@expected = [stable_tag('5.1.15')]
end
end
context 'when version is in a release date format' do
it 'detects and returns it' do
@file = 's2member.txt'
@expected = [stable_tag('141007')]
end
end
context 'when version contains letters' do
it 'returns it' do
@file = 'beta1.txt'
@expected = [stable_tag('2.0.0-beta1')]
end
end
context 'when parsing the changelog for version numbers' do
{
'changelog_version' => '1.3',
'wp_polls' => '2.64',
'nextgen_gallery' => '2.0.66.33',
'wp_user_frontend' => '1.2.3',
'my_calendar' => '2.1.5',
'nextgen_gallery_2' => '1.9.13',
'advanced-most-recent-posts-mod' => '1.6.5.2',
'a-lead-capture-contact-form-and-tab-button-by-awebvoicecom' => '3.1',
'backup-scheduler' => '1.5.9',
'release_date_slash' => '1.0.4'
}. each do |file, version_number|
context "whith #{file}.txt" do
it 'returns the expected version' do
@file = "#{file}.txt"
@expected = [changelog_section(version_number)]
end
end
end
end
end
end

View File

@@ -0,0 +1,46 @@
require 'spec_helper'
# If this file is tested alone (rspec path-to-this-file), then there will be an error about
# constants not being intilialized. This is due to the Dynamic Finders.
describe WPScan::Finders::PluginVersion::Base do
subject(:plugin_version) { described_class.new(plugin) }
let(:plugin) { WPScan::Plugin.new(slug, target) }
let(:target) { WPScan::Target.new('http://wp.lab/') }
let(:default_finders) { %w[Readme] }
describe '#finders' do
after do
expect(target).to receive(:content_dir).and_return('wp-content')
expect(plugin_version.finders.map { |f| f.class.to_s.demodulize }).to match_array @expected
end
context 'when no related specific finders' do
let(:slug) { 'spec' }
it 'contains the default finders' do
@expected = default_finders
end
end
# Dynamic Version Finders are not tested here, they are in
# spec/lib/finders/dynamic_finder/plugin_versions_spec
context 'when specific finders' do
let(:specific) do
{
# None so far
}
end
WPScan::DB::DynamicFinders::Plugin.versions_finders_configs.each do |plugin_slug, configs|
context "when #{plugin_slug} plugin" do
let(:slug) { plugin_slug }
it 'contains the expected finders (default + specific + the dynamic ones)' do
@expected = default_finders + [*specific[plugin_slug]] + configs.keys
end
end
end
end
end
end

View File

@@ -0,0 +1,13 @@
require 'spec_helper'
describe WPScan::Finders::Plugins::BodyPattern do
it_behaves_like WPScan::Finders::DynamicFinder::WpItems::Finder do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url) }
let(:url) { 'http://wp.lab/' }
let(:fixtures) { File.join(DYNAMIC_FINDERS_FIXTURES, 'plugin_version') }
let(:expected_all) { df_expected_all['plugins'] }
let(:item_class) { WPScan::Plugin }
end
end

View File

@@ -0,0 +1,13 @@
require 'spec_helper'
describe WPScan::Finders::Plugins::Comment do
it_behaves_like WPScan::Finders::DynamicFinder::WpItems::Finder do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url) }
let(:url) { 'http://wp.lab/' }
let(:fixtures) { File.join(DYNAMIC_FINDERS_FIXTURES, 'plugin_version') }
let(:expected_all) { df_expected_all['plugins'] }
let(:item_class) { WPScan::Plugin }
end
end

View File

@@ -0,0 +1,15 @@
require 'spec_helper'
describe WPScan::Finders::Plugins::ConfigParser do
xit
# it_behaves_like WPScan::Finders::DynamicFinder::WpItems::Finder do
# subject(:finder) { described_class.new(target) }
# let(:target) { WPScan::Target.new(url) }
# let(:url) { 'http://wp.lab/' }
# let(:fixtures) { File.join(DYNAMIC_FINDERS_FIXTURES, 'plugin_version') }
#
# let(:expected_all) { df_expected_all['plugins'] }
# let(:item_class) { WPScan::Plugin }
# end
end

View File

@@ -0,0 +1,45 @@
require 'spec_helper'
describe WPScan::Finders::Plugins::HeaderPattern do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url) }
let(:url) { 'http://wp.lab/' }
let(:fixtures) { File.join(DYNAMIC_FINDERS_FIXTURES, 'plugin_version') }
def plugin(slug)
WPScan::Plugin.new(slug, target)
end
describe '#passive' do
after do
stub_request(:get, target.url).to_return(headers: headers)
found = finder.passive
expect(found).to match_array @expected
expect(found.first.found_by).to eql 'Header Pattern (Passive Detection)' unless found.empty?
end
context 'when empty headers' do
let(:headers) { {} }
it 'returns an empty array' do
@expected = []
end
end
context 'when headers' do
before { expect(target).to receive(:content_dir).and_return('wp-content') }
let(:headers) { JSON.parse(File.read(File.join(fixtures, 'header_pattern_passive_all.html'))) }
it 'returns the expected plugins' do
@expected = []
WPScan::DB::DynamicFinders::Plugin.passive_header_pattern_finder_configs.each_key do |slug|
@expected << plugin(slug)
end
end
end
end
end

View File

@@ -0,0 +1,13 @@
require 'spec_helper'
describe WPScan::Finders::Plugins::JavascriptVar do
it_behaves_like WPScan::Finders::DynamicFinder::WpItems::Finder do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url) }
let(:url) { 'http://wp.lab/' }
let(:fixtures) { File.join(DYNAMIC_FINDERS_FIXTURES, 'plugin_version') }
let(:expected_all) { df_expected_all['plugins'] }
let(:item_class) { WPScan::Plugin }
end
end

View File

@@ -0,0 +1,12 @@
require 'spec_helper'
describe WPScan::Finders::Plugins::KnownLocations do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url) }
let(:url) { 'http://ex.lo/' }
let(:fixtures) { File.join(FINDERS_FIXTURES, 'plugins', 'known_locations') }
describe '#aggressive' do
xit
end
end

View File

@@ -0,0 +1,16 @@
require 'spec_helper'
describe WPScan::Finders::Plugins::QueryParameter do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url) }
let(:url) { 'http://wp.lab/' }
let(:fixtures) { File.join(DYNAMIC_FINDERS_FIXTURES, 'plugin_version') }
describe '#passive' do
its(:passive) { should be nil }
end
describe '#aggressive' do
xit
end
end

View File

@@ -0,0 +1,27 @@
require 'spec_helper'
describe WPScan::Finders::Plugins::UrlsInHomepage do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url) }
let(:url) { 'http://wp.lab/' }
let(:fixtures) { File.join(FINDERS_FIXTURES, 'plugins', 'urls_in_homepage') }
it_behaves_like 'App::Finders::WpItems::URLsInHomepage' do
let(:type) { 'plugins' }
let(:uniq_links) { true }
let(:uniq_codes) { true }
let(:expected_from_links) { (1..4).map { |i| "dl-#{i}" } }
let(:expected_from_codes) { (1..6).map { |i| "dc-#{i}" } }
end
describe '#passive' do
before do
stub_request(:get, finder.target.url)
.to_return(body: File.read(File.join(fixtures, 'found.html')))
expect(finder.target).to receive(:content_dir).at_least(1).and_return('wp-content')
end
xit
end
end

View File

@@ -0,0 +1,13 @@
require 'spec_helper'
describe WPScan::Finders::Plugins::Xpath do
it_behaves_like WPScan::Finders::DynamicFinder::WpItems::Finder do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url) }
let(:url) { 'http://wp.lab/' }
let(:fixtures) { File.join(DYNAMIC_FINDERS_FIXTURES, 'plugin_version') }
let(:expected_all) { df_expected_all['plugins'] }
let(:item_class) { WPScan::Plugin }
end
end

View File

@@ -0,0 +1,14 @@
require 'spec_helper'
describe WPScan::Finders::Plugins::Base do
subject(:plugins) { described_class.new(target) }
let(:target) { WPScan::Target.new(url) }
let(:url) { 'http://ex.lo/' }
describe '#finders' do
it 'contains the expected finders' do
expect(plugins.finders.map { |f| f.class.to_s.demodulize })
.to eq %w[UrlsInHomepage HeaderPattern Comment Xpath BodyPattern JavascriptVar KnownLocations]
end
end
end

View File

@@ -0,0 +1,98 @@
require 'spec_helper'
describe WPScan::Finders::ThemeVersion::Style do
subject(:finder) { described_class.new(theme) }
let(:theme) { WPScan::Theme.new('spec', target) }
let(:target) { WPScan::Target.new('http://wp.lab/') }
let(:fixtures) { File.join(FINDERS_FIXTURES, 'theme_version', 'style') }
before :all do
Typhoeus::Config.cache = WPScan::Cache::Typhoeus.new(File.join(SPECS, 'cache'))
end
before do
expect(target).to receive(:content_dir).at_least(1).and_return('wp-content')
stub_request(:get, /.*.css/).and_return(body: defined?(style_body) ? style_body : '')
end
describe '#passive' do
before { expect(finder).to receive(:cached_style?).and_return(cached?) }
after { finder.passive }
context 'when the style_url request has been cached' do
let(:cached?) { true }
it 'calls the style_version' do
expect(finder).to receive(:style_version)
end
end
context 'when the style_url request has not been cached' do
let(:cached?) { false }
it 'returns nil' do
expect(finder).to_not receive(:style_version)
end
end
end
describe '#aggressive' do
before { expect(finder).to receive(:cached_style?).and_return(cached?) }
after { finder.aggressive }
context 'when the style_url request has been cached' do
let(:cached?) { true }
it 'returns nil' do
expect(finder).to_not receive(:style_version)
end
end
context 'when the style_url request has not been cached' do
let(:cached?) { false }
it 'calls the style_version' do
expect(finder).to receive(:style_version)
end
end
end
describe '#cached_style?' do
it 'calls the Cache with the correct arguments' do
expected = Typhoeus::Request.new(
theme.style_url,
finder.browser.default_request_params.merge(method: :get)
)
expect(Typhoeus::Config.cache).to receive(:get) { |arg| expect(arg).to eql expected }
finder.cached_style?
end
end
describe '#style_version' do
{
'inline' => '1.5.1',
'firefart' => '1.0.0',
'tralling_quote' => '1.3',
'no_version_tag' => nil,
'trunk_version' => nil,
'no_version' => nil
}.each do |file, expected_version|
context "when #{file}" do
let(:style_body) { File.new(File.join(fixtures, "#{file}.css")) }
it 'returns the expected version' do
expected = if expected_version
WPScan::Version.new(
expected_version,
confidence: 80,
interesting_entries: ["#{theme.style_url}, Version: #{expected_version}"]
)
end
expect(finder.style_version).to eql expected
end
end
end
end
end

View File

@@ -0,0 +1,41 @@
require 'spec_helper'
describe WPScan::Finders::ThemeVersion::WooFrameworkMetaGenerator do
subject(:finder) { described_class.new(theme) }
let(:theme) { WPScan::Theme.new(slug, target) }
let(:target) { WPScan::Target.new('http://wp.lab/') }
let(:fixtures) { File.join(FINDERS_FIXTURES, 'theme_version', 'woo_framework_meta_generator') }
before do
expect(target).to receive(:content_dir).and_return('wp-content')
stub_request(:get, /\.css\z/)
end
describe '#passive' do
after do
stub_request(:get, target.url).to_return(body: File.read(File.join(fixtures, 'editorial-1.3.5.html')))
expect(finder.passive).to eql @expected
end
context 'when the theme slug does not match' do
let(:slug) { 'spec' }
it 'returns nil' do
@expected = nil
end
end
context 'when the theme slug matches' do
let(:slug) { 'Editorial' }
it 'return the expected version' do
@expected = WPScan::Version.new(
'1.3.5',
found_by: 'Woo Framework Meta Generator (Passive Detection)',
confidence: 80
)
end
end
end
end

View File

@@ -0,0 +1,35 @@
require 'spec_helper'
describe WPScan::Finders::ThemeVersion::Base do
subject(:theme_version) { described_class.new(theme) }
let(:theme) { WPScan::Plugin.new(slug, target) }
let(:target) { WPScan::Target.new('http://wp.lab/') }
let(:slug) { 'spec' }
let(:default_finders) { %w[Style WooFrameworkMetaGenerator] }
describe '#finders' do
after do
expect(target).to receive(:content_dir).and_return('wp-content')
expect(theme_version.finders.map { |f| f.class.to_s.demodulize }).to eql @expected
end
context 'when no related specific finders' do
it 'contains the default finders' do
@expected = default_finders
end
end
context 'when specific finders' do
{
}.each do |theme_slug, specific_finders|
context "when #{theme_slug} theme" do
let(:slug) { theme_slug }
it 'contains the expected finders' do
@expected = default_finders + specific_finders
end
end
end
end
end
end

View File

@@ -0,0 +1,12 @@
require 'spec_helper'
describe WPScan::Finders::Themes::KnownLocations do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url) }
let(:url) { 'http://ex.lo/' }
let(:fixtures) { File.join(FINDERS_FIXTURES, 'themes', 'known_locations') }
describe '#aggressive' do
xit
end
end

View File

@@ -0,0 +1,20 @@
require 'spec_helper'
describe WPScan::Finders::Themes::UrlsInHomepage do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url) }
let(:url) { 'http://wp.lab/' }
let(:fixtures) { File.join(FINDERS_FIXTURES, 'themes', 'urls_in_homepage') }
it_behaves_like 'App::Finders::WpItems::URLsInHomepage' do
let(:type) { 'themes' }
let(:uniq_links) { true }
let(:uniq_codes) { true }
let(:expected_from_links) { %w[dl-1] }
let(:expected_from_codes) { %w[dc-1] }
end
describe '#passive' do
xit
end
end

View File

@@ -0,0 +1,14 @@
require 'spec_helper'
describe WPScan::Finders::Themes::Base do
subject(:themes) { described_class.new(target) }
let(:target) { WPScan::Target.new(url) }
let(:url) { 'http://ex.lo/' }
describe '#finders' do
it 'contains the expected finders' do
expect(themes.finders.map { |f| f.class.to_s.demodulize })
.to eq %w[UrlsInHomepage KnownLocations]
end
end
end

View File

@@ -0,0 +1,36 @@
require 'spec_helper'
describe WPScan::Finders::TimthumbVersion::BadRequest do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Timthumb.new(url) }
let(:url) { 'http://ex.lo/timthumb.php' }
let(:fixtures) { File.join(FINDERS_FIXTURES, 'timthumb_version', 'bad_request') }
describe '#aggressive' do
before { stub_request(:get, url).to_return(body: File.read(File.join(fixtures, file))) }
after { expect(finder.aggressive).to eql @expected }
context 'when no version' do
let(:file) { 'no_version.php' }
it 'returns nil' do
@expected = nil
end
end
context 'when a version' do
let(:file) { '2.8.14.php' }
it 'returns the expected version' do
@expected = WPScan::Version.new(
'2.8.14',
confidence: 90,
found_by: 'Bad Request (Aggressive Detection)',
interesting_entries: [
"#{url}, TimThumb version : 2.8.14"
]
)
end
end
end
end

View File

@@ -0,0 +1,13 @@
require 'spec_helper'
describe WPScan::Finders::TimthumbVersion::Base do
subject(:timthumb_version) { described_class.new(target) }
let(:target) { WPScan::Timthumb.new(url) }
let(:url) { 'http://ex.lo/timthumb.php' }
describe '#finders' do
it 'contains the expected finders' do
expect(timthumb_version.finders.map { |f| f.class.to_s.demodulize }).to eq %w[BadRequest]
end
end
end

View File

@@ -0,0 +1,12 @@
require 'spec_helper'
describe WPScan::Finders::Timthumbs::KnownLocations do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url) }
let(:url) { 'http://ex.lo/' }
let(:fixtures) { File.join(FINDERS_FIXTURES, 'timthumbs', 'known_locations') }
describe '#aggressive' do
xit
end
end

View File

@@ -0,0 +1,13 @@
require 'spec_helper'
describe WPScan::Finders::Timthumbs::Base do
subject(:timthumb) { described_class.new(target) }
let(:target) { WPScan::Target.new(url) }
let(:url) { 'http://ex.lo/' }
describe '#finders' do
it 'contains the expected finders' do
expect(timthumb.finders.map { |f| f.class.to_s.demodulize }).to eq %w[KnownLocations]
end
end
end

View File

@@ -0,0 +1,62 @@
require 'spec_helper'
describe WPScan::Finders::Users::AuthorIdBruteForcing do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url) }
let(:url) { 'http://wp.lab/' }
let(:fixtures) { File.join(FINDERS_FIXTURES, 'users', 'author_id_brute_forcing') }
describe '#aggressive' do
xit
end
describe '#target_urls' do
it 'returns the correct URLs' do
expect(finder.target_urls(range: (1..2))).to eql(
url + '?author=1' => 1,
url + '?author=2' => 2
)
end
end
describe '#potential_username' do
[
'4.1.1', '4.1.1-permalink',
'3.0', '3.0-permalink',
'2.9.2', '2.9.2-permalink'
].each do |file|
it "returns 'admin' from #{file}.html" do
body = File.read(File.join(fixtures, "#{file}.html"))
res = Typhoeus::Response.new(body: body)
expect(finder.username_from_response(res)).to eql 'admin'
end
end
end
describe '#display_name_from_body' do
context 'when display name' do
[
'4.1.1', '4.1.1-permalink',
'3.0', '3.0-permalink',
'2.9.2', '2.9.2-permalink'
].each do |file|
it "returns 'admin display_name' from #{file}.html" do
body = File.read(File.join(fixtures, "#{file}.html"))
expect(finder.display_name_from_body(body)).to eql 'admin display_name'
end
end
end
context 'when no display_name' do
['4.1.1', '3.0', '2.9.2'].each do |file|
it "returns nil for #{file}-empty.html" do
body = File.read(File.join(fixtures, "#{file}-empty.html"))
expect(finder.display_name_from_body(body)).to eql nil
end
end
end
end
end

View File

@@ -0,0 +1,27 @@
require 'spec_helper'
describe WPScan::Finders::Users::AuthorPosts do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url) }
let(:url) { 'http://wp.lab/' }
let(:fixtures) { File.join(FINDERS_FIXTURES, 'users', 'author_posts') }
describe '#passive' do
xit
end
describe '#potential_usernames' do
it 'returns the expected usernames' do
res = Typhoeus::Response.new(body: File.read(File.join(fixtures, 'potential_usernames.html')))
results = finder.potential_usernames(res)
expect(results).to eql([
['admin', 'Author Pattern', 100],
['admin display_name', 'Display Name', 30],
['editor', 'Author Pattern', 100],
['editor', 'Display Name', 30]
])
end
end
end

View File

@@ -0,0 +1,32 @@
require 'spec_helper'
describe WPScan::Finders::Users::LoginErrorMessages do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url) }
let(:url) { 'http://wp.lab/' }
let(:fixtures) { File.join(FINDERS_FIXTURES, 'users', 'login_error_messages') }
describe '#aggressive' do
xit
end
describe '#usernames' do
let(:opts) { { found: [] } }
after { expect(subject.usernames(opts)).to eql @expected }
context 'when no :list provided' do
it 'returns an empty list' do
@expected = []
end
end
context 'when :list provided' do
let(:opts) { super().merge(list: %w[u1 u2]) }
it 'returns the expected array' do
@expected = opts[:list]
end
end
end
end

View File

@@ -0,0 +1,12 @@
require 'spec_helper'
describe WPScan::Finders::Users::OembedApi do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url) }
let(:url) { 'http://wp.lab/' }
let(:fixtures) { File.join(FINDERS_FIXTURES, 'users', 'oembed_api') }
describe '#aggressive' do
xit
end
end

View File

@@ -0,0 +1,102 @@
require 'spec_helper'
describe WPScan::Finders::Users::RSSGenerator do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url) }
let(:url) { 'http://ex.lo/' }
let(:fixtures) { Pathname.new(FINDERS_FIXTURES).join('users', 'rss_generator') }
let(:rss_fixture) { File.read(fixtures.join('feed.xml')) }
describe '#passive, #aggressive' do
before do
allow(target).to receive(:sub_dir).and_return(false)
stub_request(:get, target.url).to_return(body: File.read(homepage_fixture))
end
context 'when no RSS link in homepage' do
let(:homepage_fixture) { fixtures.join('homepage_no_links.html') }
its(:passive) { should eql [] }
it 'returns the expected from #aggressive' do
stub_request(:get, target.url('feed/')).to_return(body: rss_fixture)
stub_request(:get, target.url('comments/feed/'))
stub_request(:get, target.url('feed/rss/'))
stub_request(:get, target.url('feed/rss2/'))
expect(finder.aggressive).to eql [
CMSScanner::User.new(
'admin',
confidence: 50,
found_by: 'Rss Generator (Aggressive Detection)'
),
CMSScanner::User.new(
'Aa Días-Gildés',
confidence: 50,
found_by: 'Rss Generator (Aggressive Detection)'
)
]
end
end
context 'when RSS link in homepage' do
let(:homepage_fixture) { File.join(fixtures, 'homepage_links.html') }
it 'returns the expected from #passive' do
stub_request(:get, target.url('feed/')).to_return(body: rss_fixture)
expect(finder.passive).to eql [
CMSScanner::User.new(
'admin',
confidence: 50,
found_by: 'Rss Generator (Passive Detection)'
),
CMSScanner::User.new(
'Aa Días-Gildés',
confidence: 50,
found_by: 'Rss Generator (Passive Detection)'
)
]
end
context 'when :mixed mode' do
it 'avoids checking existing URL/s from #passive' do
stub_request(:get, target.url('comments/feed/')).to_return(body: rss_fixture)
expect(finder.aggressive(mode: :mixed)).to eql [
CMSScanner::User.new(
'admin',
confidence: 50,
found_by: 'Rss Generator (Aggressive Detection)'
),
CMSScanner::User.new(
'Aa Días-Gildés',
confidence: 50,
found_by: 'Rss Generator (Aggressive Detection)'
)
]
end
end
context 'when no mode' do
it 'checks the first URL detected from the URLs' do
stub_request(:get, target.url('feed/')).to_return(body: rss_fixture)
expect(finder.aggressive).to eql [
CMSScanner::User.new(
'admin',
confidence: 50,
found_by: 'Rss Generator (Aggressive Detection)'
),
CMSScanner::User.new(
'Aa Días-Gildés',
confidence: 50,
found_by: 'Rss Generator (Aggressive Detection)'
)
]
end
end
end
end
end

View File

@@ -0,0 +1,47 @@
require 'spec_helper'
describe WPScan::Finders::Users::WpJsonApi do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url) }
let(:url) { 'http://wp.lab/' }
let(:fixtures) { File.join(FINDERS_FIXTURES, 'users', 'wp_json_api') }
describe '#aggressive' do
before do
# allow(target).to receive(:content_dir).and_return('wp-content')
allow(target).to receive(:sub_dir).and_return(false)
stub_request(:get, finder.api_url).to_return(body: body)
end
context 'when not a JSON response' do
let(:body) { '' }
its(:aggressive) { should eql([]) }
end
context 'when a JSON response' do
context 'when unauthorised' do
let(:body) { File.read(File.join(fixtures, '401.json')) }
its(:aggressive) { should eql([]) }
end
context 'when limited exposure (WP >= 4.7.1)' do
let(:body) { File.read(File.join(fixtures, '4.7.2.json')) }
it 'returns the expected array of users' do
users = finder.aggressive
expect(users.size).to eql 1
user = users.first
expect(user.id).to eql 1
expect(user.username).to eql 'admin'
expect(user.confidence).to eql 100
expect(user.interesting_entries).to eql ['http://wp.lab/wp-json/wp/v2/users/']
end
end
end
end
end

View File

@@ -0,0 +1,14 @@
require 'spec_helper'
describe WPScan::Finders::Users::Base do
subject(:user) { described_class.new(target) }
let(:target) { WPScan::Target.new(url) }
let(:url) { 'http://ex.lo/' }
describe '#finders' do
it 'contains the expected finders' do
expect(user.finders.map { |f| f.class.to_s.demodulize })
.to eq %w[AuthorPosts WpJsonApi OembedApi RSSGenerator AuthorIdBruteForcing LoginErrorMessages]
end
end
end

View File

@@ -0,0 +1,97 @@
require 'spec_helper'
describe WPScan::Finders::WpVersion::AtomGenerator do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url).extend(CMSScanner::Target::Server::Apache) }
let(:url) { 'http://ex.lo/' }
let(:fixtures) { Pathname.new(FINDERS_FIXTURES).join('wp_version', 'atom_generator') }
let(:atom_fixture) { File.read(fixtures.join('feed', 'atom')) }
describe '#passive, #aggressive' do
before do
allow(target).to receive(:sub_dir).and_return(false)
stub_request(:get, target.url).to_return(body: File.read(homepage_fixture))
end
context 'when no atom links in homepage' do
let(:homepage_fixture) { fixtures.join('no_links.html') }
its(:passive) { should eql [] }
it 'returns the expected from #aggressive' do
stub_request(:get, target.url('feed/atom/')).to_return(body: atom_fixture)
stub_request(:get, target.url('?feed=atom'))
expect(finder.aggressive).to eql [
WPScan::WpVersion.new(
'4.0',
confidence: 80,
found_by: 'Atom Generator (Aggressive Detection)',
interesting_entries: [
"#{target.url('feed/atom/')}, Match: '<generator uri=\"https://wordpress.org/\" version=\"4.0\">" \
"WordPress</generator>'"
]
)
]
end
end
context 'when atom links in homepage' do
let(:homepage_fixture) { File.join(fixtures, 'links.html') }
it 'returns the expected from #passive' do
stub_request(:get, target.url('?feed=atom')).to_return(body: atom_fixture)
expect(finder.passive).to eql [
WPScan::WpVersion.new(
'4.0',
confidence: 80,
found_by: 'Atom Generator (Passive Detection)',
interesting_entries: [
"#{target.url('?feed=atom')}, Match: '<generator uri=\"https://wordpress.org/\" version=\"4.0\">" \
"WordPress</generator>'"
]
)
]
end
context 'when :mixed mode' do
it 'avoids checking existing URL/s from #passive' do
stub_request(:get, target.url('feed/atom/')).to_return(body: atom_fixture)
expect(finder.aggressive(mode: :mixed)).to eql [
WPScan::WpVersion.new(
'4.0',
confidence: 80,
found_by: 'Atom Generator (Aggressive Detection)',
interesting_entries: [
"#{target.url('feed/atom/')}, Match: '<generator uri=\"https://wordpress.org/\" version=\"4.0\">" \
"WordPress</generator>'"
]
)
]
end
end
context 'when no mode' do
it 'checks all the URLs' do
stub_request(:get, target.url('feed/atom/')).to_return(body: atom_fixture)
stub_request(:get, target.url('?feed=atom'))
expect(finder.aggressive).to eql [
WPScan::WpVersion.new(
'4.0',
confidence: 80,
found_by: 'Atom Generator (Aggressive Detection)',
interesting_entries: [
"#{target.url('feed/atom/')}, Match: '<generator uri=\"https://wordpress.org/\" version=\"4.0\">" \
"WordPress</generator>'"
]
)
]
end
end
end
end
end

View File

@@ -0,0 +1,10 @@
require 'spec_helper'
describe WPScan::Finders::WpVersion::RDFGenerator do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url).extend(CMSScanner::Target::Server::Apache) }
let(:url) { 'http://ex.lo/' }
let(:fixtures) { File.join(FINDERS_FIXTURES, 'wp_version', 'rdf_generator') }
xit
end

View File

@@ -0,0 +1,49 @@
require 'spec_helper'
describe WPScan::Finders::WpVersion::Readme do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url).extend(CMSScanner::Target::Server::Apache) }
let(:url) { 'http://ex.lo/' }
let(:fixtures) { File.join(FINDERS_FIXTURES, 'wp_version', 'readme') }
let(:readme_url) { url + 'readme.html' }
describe '#aggressive' do
before { stub_request(:get, readme_url).to_return(body: File.read(File.join(fixtures, file))) }
after do
expect(target).to receive(:sub_dir).and_return(false)
expect(finder.aggressive).to eql @expected
end
context 'when no version' do
let(:file) { 'no_version.html' }
it 'returns nil' do
@expected = nil
end
end
context 'when invalid version number' do
let(:file) { 'invalid.html' }
it 'returns nil' do
@expected = nil
end
end
context 'when present and valid' do
let(:file) { '4.0.html' }
it 'returns the expected version' do
@expected = WPScan::WpVersion.new(
'4.0',
confidence: 90,
found_by: 'Readme (Aggressive Detection)',
interesting_entries: [
"#{readme_url}, Match: 'Version 4.0'"
]
)
end
end
end
end

View File

@@ -0,0 +1,10 @@
require 'spec_helper'
describe WPScan::Finders::WpVersion::RSSGenerator do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url).extend(CMSScanner::Target::Server::Apache) }
let(:url) { 'http://ex.lo/' }
let(:fixtures) { File.join(FINDERS_FIXTURES, 'wp_version', 'rss_generator') }
xit
end

View File

@@ -0,0 +1,10 @@
require 'spec_helper'
describe WPScan::Finders::WpVersion::UniqueFingerprinting do
subject(:finder) { described_class.new(target) }
let(:target) { WPScan::Target.new(url).extend(CMSScanner::Target::Server::Apache) }
let(:url) { 'http://ex.lo/' }
let(:fixtures) { File.join(FINDERS_FIXTURES, 'wp_version', 'unique_fingerprinting') }
xit
end

View File

@@ -0,0 +1,25 @@
require 'spec_helper'
# If this file is tested alone (rspec path-to-this-file), then there will be an error about
# constants not being intilialized. This is due to the Dynamic Finders.
describe WPScan::Finders::WpVersion::Base do
subject(:wp_version) { described_class.new(target) }
let(:target) { WPScan::Target.new(url) }
let(:url) { 'http://ex.lo/' }
describe '#finders' do
let(:expected) { %w[RSSGenerator AtomGenerator RDFGenerator Readme UniqueFingerprinting] }
let(:expected_dynamic_finders) { WPScan::DB::DynamicFinders::Wordpress.versions_finders_configs.keys }
it 'contains the expected finders' do
finders = wp_version.finders.map { |f| f.class.to_s.demodulize }
expect(finders).to match_array expected + expected_dynamic_finders
expect(finders.first).to eql 'RSSGenerator'
expect(finders.last).to eql 'UniqueFingerprinting'
end
end
end

View File

@@ -0,0 +1,9 @@
require 'spec_helper'
describe WPScan::InterestingFinding do
it_behaves_like WPScan::References do
subject(:finding) { described_class.new('http://e.org/file.php', opts) }
let(:opts) { { references: references } }
let(:references) { {} }
end
end

View File

@@ -0,0 +1,10 @@
require 'spec_helper'
describe WPScan::Media do
subject(:media) { described_class.new(url) }
let(:url) { 'http://e.oeg/?attachment_id=2' }
describe '#new' do
its(:url) { should eql url }
end
end

View File

@@ -0,0 +1,192 @@
require 'spec_helper'
describe WPScan::Plugin do
subject(:plugin) { described_class.new(slug, blog, opts) }
let(:slug) { 'spec' }
let(:blog) { WPScan::Target.new('http://wp.lab/') }
let(:opts) { {} }
before { expect(blog).to receive(:content_dir).and_return('wp-content') }
describe '#new' do
its(:url) { should eql 'http://wp.lab/wp-content/plugins/spec/' }
end
describe '#version' do
after do
expect(WPScan::Finders::PluginVersion::Base).to receive(:find).with(plugin, @expected_opts)
plugin.version(version_opts)
end
let(:default_opts) { {} }
context 'when no :detection_mode' do
context 'when no :mode opt supplied' do
let(:version_opts) { { something: 'k' } }
it 'calls the finder with the correct parameters' do
@expected_opts = version_opts
end
end
context 'when :mode supplied' do
let(:version_opts) { { mode: :passive } }
it 'calls the finder with the correct parameters' do
@expected_opts = default_opts.merge(mode: :passive)
end
end
end
context 'when :detection_mode' do
let(:opts) { super().merge(mode: :passive) }
context 'when no :mode' do
let(:version_opts) { {} }
it 'calls the finder without mode' do
@expected_opts = version_opts
end
end
context 'when :mode' do
let(:version_opts) { { mode: :mixed } }
it 'calls the finder with the :mixed mode' do
@expected_opts = default_opts.merge(mode: :mixed)
end
end
end
end
describe '#latest_version, #last_updated, #popular' do
context 'when none' do
let(:slug) { 'vulnerable-not-popular' }
its(:latest_version) { should be_nil }
its(:last_updated) { should be_nil }
its(:popular?) { should be false }
end
context 'when values' do
let(:slug) { 'no-vulns-popular' }
its(:latest_version) { should eql WPScan::Version.new('2.0') }
its(:last_updated) { should eql '2015-05-16T00:00:00.000Z' }
its(:popular?) { should be true }
end
end
describe '#outdated?' do
context 'when last_version' do
let(:slug) { 'no-vulns-popular' }
context 'when no version' do
before { expect(plugin).to receive(:version).at_least(1).and_return(nil) }
its(:outdated?) { should eql false }
end
context 'when version' do
before { expect(plugin).to receive(:version).at_least(1).and_return(WPScan::Version.new(version_number)) }
context 'when version < last_version' do
let(:version_number) { '1.2' }
its(:outdated?) { should eql true }
end
context 'when version >= last_version' do
let(:version_number) { '3.0' }
its(:outdated?) { should eql false }
end
end
end
context 'when no last_version' do
let(:slug) { 'vulnerable-not-popular' }
context 'when no version' do
before { expect(plugin).to receive(:version).at_least(1).and_return(nil) }
its(:outdated?) { should eql false }
end
context 'when version' do
before { expect(plugin).to receive(:version).at_least(1).and_return(WPScan::Version.new('1.0')) }
its(:outdated?) { should eql false }
end
end
end
describe '#vulnerabilities' do
after do
expect(plugin.vulnerabilities).to eq @expected
expect(plugin.vulnerable?).to eql @expected.empty? ? false : true
end
context 'when plugin not in the DB' do
let(:slug) { 'not-in-db' }
it 'returns an empty array' do
@expected = []
end
end
context 'when in the DB' do
context 'when no vulnerabilities' do
let(:slug) { 'no-vulns-popular' }
it 'returns an empty array' do
@expected = []
end
end
context 'when vulnerabilities' do
let(:slug) { 'vulnerable-not-popular' }
let(:all_vulns) do
[
WPScan::Vulnerability.new(
'First Vuln',
{ wpvulndb: '1' },
'LFI',
'6.3.10'
),
WPScan::Vulnerability.new('No Fixed In', wpvulndb: '2')
]
end
context 'when no plugin version' do
before { expect(plugin).to receive(:version).at_least(1).and_return(false) }
it 'returns all the vulnerabilities' do
@expected = all_vulns
end
end
context 'when plugin version' do
before { expect(plugin).to receive(:version).at_least(1).and_return(WPScan::Version.new(number)) }
context 'when < to a fixed_in' do
let(:number) { '5.0' }
it 'returns it' do
@expected = all_vulns
end
end
context 'when >= to a fixed_in' do
let(:number) { '6.3.10' }
it 'does not return it ' do
@expected = [all_vulns.last]
end
end
end
end
end
end
end

View File

@@ -0,0 +1,165 @@
require 'spec_helper'
describe WPScan::Theme do
subject(:theme) { described_class.new(slug, blog, opts) }
let(:slug) { 'spec' }
let(:blog) { WPScan::Target.new('http://wp.lab/') }
let(:opts) { {} }
let(:fixtures) { File.join(FIXTURES, 'models', 'theme') }
before { expect(blog).to receive(:content_dir).at_least(1).and_return('wp-content') }
describe '#new' do
before do
stub_request(:get, /.*\.css\z/)
.to_return(body: File.read(File.join(fixtures, 'style.css')))
end
its(:url) { should eql 'http://wp.lab/wp-content/themes/spec/' }
its(:style_url) { should eql 'http://wp.lab/wp-content/themes/spec/style.css' }
its(:style_name) { should eql 'Twenty Fifteen' }
its(:style_uri) { should eql 'https://wordpress.org/themes/twentyfifteen' }
its(:author) { should eql 'the WordPress team' }
its(:author_uri) { should eql nil }
its(:template) { should eql nil }
its(:description) { should eql 'Our 2015 default theme is clean, blog-focused.' }
its(:license) { should eql 'GNU General Public License v2 or later' }
its(:license_uri) { should eql 'http://www.gnu.org/licenses/gpl-2.0.html' }
its(:tags) { should eql 'black, blue, gray, pink, purple, white, yellow.' }
its(:text_domain) { should eql 'twentyfifteen' }
context 'when opts[:style_url]' do
let(:opts) { super().merge(style_url: 'http://wp.lab/wp-content/themes/spec/custom.css') }
its(:style_url) { should eql opts[:style_url] }
end
end
describe '#version' do
after do
stub_request(:get, /.*\.css\z/)
.to_return(body: File.read(File.join(fixtures, 'style.css')))
expect(WPScan::Finders::ThemeVersion::Base).to receive(:find).with(theme, @expected_opts)
theme.version(version_opts)
end
let(:default_opts) { {} }
context 'when no :detection_mode' do
context 'when no :mode opt supplied' do
let(:version_opts) { { something: 'k' } }
it 'calls the finder with the correct parameters' do
@expected_opts = version_opts
end
end
context 'when :mode supplied' do
let(:version_opts) { { mode: :passive } }
it 'calls the finder with the correct parameters' do
@expected_opts = default_opts.merge(mode: :passive)
end
end
end
context 'when :detection_mode' do
let(:opts) { super().merge(mode: :passive) }
context 'when no :mode' do
let(:version_opts) { {} }
it 'calls the finder without mode' do
@expected_opts = version_opts
end
end
context 'when :mode' do
let(:version_opts) { { mode: :mixed } }
it 'calls the finder with the :mixed mode' do
@expected_opts = default_opts.merge(mode: :mixed)
end
end
end
end
describe '#vulnerabilities' do
xit
end
describe '#parent_theme' do
before do
stub_request(:get, blog.url('wp-content/themes/spec/style.css'))
.to_return(body: File.read(File.join(fixtures, main_theme)))
end
context 'when no template' do
let(:main_theme) { 'style.css' }
it 'returns nil' do
expect(theme.parent_theme).to eql nil
end
end
context 'when a template' do
let(:main_theme) { 'child_style.css' }
let(:parent_url) { blog.url('wp-content/themes/twentyfourteen/custom.css') }
before do
stub_request(:get, parent_url)
.to_return(body: File.read(File.join(fixtures, 'style.css')))
end
%w[child_style windows_line_endings].each do |fixture|
context "when #{fixture}" do
let(:main_theme) { "#{fixture}.css" }
it 'returns the expected theme' do
parent = theme.parent_theme
expect(parent).to eql described_class.new(
'twentyfourteen', blog,
style_url: parent_url,
confidence: 100,
found_by: 'Parent Themes (Passive Detection)'
)
expect(parent.style_url).to eql parent_url
end
end
end
end
end
describe '#parent_themes' do
xit
end
describe '#==' do
before { stub_request(:get, /.*\.css\z/) }
context 'when default style' do
it 'returns true when equal' do
expect(theme == described_class.new(slug, blog, opts)).to be true
end
it 'returns false when not equal' do
expect(theme == described_class.new(slug, blog, opts.merge(style_url: 'spec.css'))).to be false
end
end
context 'when custom style' do
let(:opts) { super().merge(style_url: 'spec.css') }
it 'returns true when equal' do
expect(theme == described_class.new(slug, blog, opts.merge(style_url: 'spec.css'))).to be true
end
it 'returns false when not equal' do
expect(theme == described_class.new(slug, blog, opts.merge(style_url: 'spec2.css'))).to be false
end
end
end
end

View File

@@ -0,0 +1,126 @@
require 'spec_helper'
describe WPScan::Timthumb do
subject(:timthumb) { described_class.new(url, opts) }
let(:url) { 'http://wp.lab/wp-content/timthumb.php' }
let(:fixtures) { File.join(FIXTURES, 'models', 'timthumb') }
let(:opts) { {} }
describe '#new' do
its(:url) { should eql url }
end
# The fact that the finders should only be called once is handled by the
# vulnerabilities, vulnerable? specs below
describe '#version' do
after do
expect(WPScan::Finders::TimthumbVersion::Base).to receive(:find).with(timthumb, @expected_opts)
timthumb.version(version_opts)
end
context 'when no :version_detection' do
context 'when no :mode opt supplied' do
let(:version_opts) { { something: 'k' } }
it 'calls the finder with the correct parameters' do
@expected_opts = version_opts
end
end
context 'when :mode supplied' do
let(:version_opts) { { mode: :passive } }
it 'calls the finder with the correct parameters' do
@expected_opts = { mode: :passive }
end
end
end
context 'when :version_detection' do
let(:opts) { super().merge(mode: :passive) }
context 'when no :mode' do
let(:version_opts) { {} }
it 'calls the finder with the :passive mode' do
@expected_opts = version_opts
end
end
context 'when :mode' do
let(:version_opts) { { mode: :mixed } }
it 'calls the finder with the :mixed mode' do
@expected_opts = { mode: :mixed }
end
end
end
end
describe '#webshot_enabled?' do
before do
stub_request(:get, /#{timthumb.url}\?src=.*&webshot=1/i)
.to_return(body: File.read(File.join(fixtures, fixture)))
end
context 'when enabled' do
let(:fixture) { '2.8.13_webshot_enabled.html' }
its(:webshot_enabled?) { should eql true }
end
context 'when disabled' do
let(:fixture) { '2.8.13_webshot_disabled.html' }
its(:webshot_enabled?) { should eql false }
end
end
describe '#vulnerabilities, #vulnerable?' do
before { expect(WPScan::Finders::TimthumbVersion::Base).to receive(:find).and_return(version) }
context 'when no version' do
let(:version) { false }
its(:vulnerabilities) { should eq([timthumb.rce_webshot_vuln, timthumb.rce_132_vuln]) }
it { should be_vulnerable }
end
context 'when version' do
let(:version) { WPScan::Version.new(version_number) }
context 'when version >= 2.8.14' do
let(:version_number) { '2.8.14' }
its(:vulnerabilities) { should eq([]) }
it { should_not be_vulnerable }
end
context 'when version < 1.33' do
let(:version_number) { '1.20' }
its(:vulnerabilities) { should eq([timthumb.rce_132_vuln]) }
it { should be_vulnerable }
end
context 'when version > 1.35 and < 2.8.13' do
let(:version_number) { '2.8.10' }
context 'when webshot enabled' do
before { expect(timthumb).to receive(:webshot_enabled?).and_return(true) }
its(:vulnerabilities) { should eq([timthumb.rce_webshot_vuln]) }
it { should be_vulnerable }
end
context 'when webshot disabled' do
before { expect(timthumb).to receive(:webshot_enabled?).and_return(false) }
its(:vulnerabilities) { should eq([]) }
it { should_not be_vulnerable }
end
end
end
end
end

View File

@@ -0,0 +1,130 @@
require 'spec_helper'
describe WPScan::WpItem do
subject(:wp_item) { described_class.new(slug, blog, opts) }
let(:slug) { 'test_item' }
let(:blog) { WPScan::Target.new(url) }
let(:url) { 'http://wp.lab/' }
let(:opts) { {} }
its(:blog) { should eql blog }
describe '#new' do
context 'when no opts' do
its(:slug) { should eql slug }
its(:detection_opts) { should eql(mode: nil) }
its(:version_detection_opts) { should eql({}) }
end
context 'when :mode' do
let(:opts) { super().merge(mode: :passive, version_detection: { mode: :aggressive }) }
its(:detection_opts) { should eql(mode: :passive) }
its(:version_detection_opts) { should eql(mode: :aggressive) }
end
context 'when the slug contains encoded chars' do
let(:slug) { 'theme%212%23a' }
its(:slug) { should eql 'theme!2#a' }
end
end
describe '#url' do
context 'when no opts[:url]' do
its(:url) { should eql nil }
end
context 'when opts[:url]' do
let(:opts) { super().merge(url: item_url) }
let(:item_url) { "#{url}item/" }
context 'when path given' do
it 'appends it' do
expect(wp_item.url('path')).to eql "#{item_url}path"
end
end
it 'encodes the path' do
expect(wp_item.url('#t#')).to eql "#{item_url}%23t%23"
expect(wp_item.url('t .txt')).to eql "#{item_url}t%20.txt"
end
end
end
describe '#==' do
context 'when the same slug' do
it 'returns true' do
other = described_class.new(slug, blog)
expect(wp_item == other).to be true
end
end
context 'when another object' do
it 'returns false' do
expect(wp_item == 'string').to be false
end
end
context 'when different slugs' do
it 'returns false' do
other = described_class.new('another', blog)
expect(wp_item == other).to be false
end
end
end
describe '#latest_version' do
# Handled in plugin_spec / theme_spec
end
describe '#popular?' do
# Handled in plugin_spec / theme_spec
end
describe '#last_updated' do
# Handled in plugin_spec / theme_spec
end
describe '#outdated?' do
# Handled in plugin_spec / theme_spec
end
describe '#to_s' do
its(:to_s) { should eql slug }
end
describe '#classify' do
its(:classify) { should eql :TestItem }
context 'when it starts with a digit' do
let(:slug) { '2test' }
its(:classify) { should eql :D_2test }
context 'when a digit and -' do
let(:slug) { '23-test' }
its(:classify) { should eql :D_23Test }
end
end
end
describe '#readme_url' do
xit
end
describe '#changelog_url' do
xit
end
describe '#directory_listing?' do
xit
end
describe '#error_log?' do
xit
end
end

View File

@@ -0,0 +1,89 @@
require 'spec_helper'
describe WPScan::WpVersion do
describe '#new' do
context 'when invalid number' do
it 'raises an error' do
expect { described_class.new('aa') }.to raise_error WPScan::InvalidWordPressVersion
end
end
context 'when valid number' do
it 'create the instance' do
version = described_class.new(4.0)
expect(version).to be_a described_class
expect(version.number).to eql '4.0'
end
end
end
describe '.all' do
it 'returns the correct values' do
expect(described_class.all).to eql %w[4.4 4.0 3.9.1 3.8.2 3.8.1 3.8]
end
end
describe '.valid?' do
after { expect(described_class.valid?(@number)).to eq @expected }
it 'returns false' do
@number = 'aaa'
@expected = false
end
it 'returns true' do
@number = '4.0'
@expected = true
end
end
describe '#vulnerabilities' do
subject(:version) { described_class.new(number) }
context 'when no vulns' do
let(:number) { '4.4' }
its(:vulnerabilities) { should eql([]) }
end
context 'when vulnerable' do
after do
expect(version.vulnerabilities).to eq @expected
expect(version).to be_vulnerable
end
context 'when a signle vuln' do
let(:number) { '3.8' }
it 'returns the expected result' do
@expected = [WPScan::Vulnerability.new(
'WP 3.8 - Vuln 1',
{ url: %w[url-4], osvdb: %w[11], wpvulndb: '3' },
'AUTHBYPASS'
)]
end
end
context 'when multiple vulns' do
let(:number) { '3.8.1' }
it 'returns the expected results' do
@expected = [
WPScan::Vulnerability.new(
'WP 3.8.1 - Vuln 1',
{ wpvulndb: '1' },
'SQLI'
),
WPScan::Vulnerability.new(
'WP 3.8.1 - Vuln 2',
{ url: %w[url-2 url-3], osvdb: %w[10], cve: %w[2014-0166], wpvulndb: '2' },
nil,
'3.8.2'
)
]
end
end
end
end
end

View File

@@ -0,0 +1,9 @@
require 'spec_helper'
describe WPScan::XMLRPC do
subject(:xml_rpc) { described_class.new('http//e.org/xmlrpc.php') }
describe '#references' do
its(:references) { should_not be_empty }
end
end

37
spec/app/views_spec.rb Normal file
View File

@@ -0,0 +1,37 @@
require 'spec_helper'
describe 'App::Views' do
let(:target_url) { 'http://ex.lo/' }
let(:target) { WPScan::Target.new(target_url) }
let(:fixtures) { File.join(SPECS, 'output') }
# CliNoColour is used to test the CLI output to avoid the painful colours
# in the expected output.
%i[JSON CliNoColour].each do |formatter|
context "when #{formatter}" do
it_behaves_like 'App::Views::WpVersion'
it_behaves_like 'App::Views::MainTheme'
it_behaves_like 'App::Views::Enumeration'
let(:parsed_options) { { url: target_url, format: formatter.to_s.underscore.dasherize } }
before do
controller.class.parsed_options = parsed_options
# Resets the formatter to ensure the correct one is loaded
controller.class.class_variable_set(:@@formatter, nil)
end
after do
view_filename = defined?(expected_view) ? expected_view : view
view_filename = "#{view_filename}.#{formatter.to_s.underscore.downcase}"
controller_dir = controller.class.to_s.demodulize.underscore.downcase
expected_output = File.read(File.join(fixtures, controller_dir, view_filename))
expect($stdout).to receive(:puts).with(expected_output)
controller.output(view, @tpl_vars)
controller.formatter.beautify # Mandatory to be able to test formatter such as JSON
end
end
end
end