WpTarget modules reworked

This commit is contained in:
erwanlr
2013-04-08 18:22:06 +02:00
parent e07bb73eeb
commit 748b5d3166
53 changed files with 1122 additions and 1360 deletions

View File

@@ -0,0 +1,57 @@
# encoding: UTF-8
shared_examples 'WpTarget::BruteForce' do
let(:fixtures_dir) { SPEC_FIXTURES_WPSCAN_WP_TARGET_DIR + '/bruteforce' }
let(:wordlist) { fixtures_dir + '/wordlist.txt' }
before :each do
wp_target.stub(:login_url).and_return('http://example.localhost/wp-login.php')
Browser.instance.max_threads = 1
end
describe '#lines_in_file' do
it 'returns 6' do
lines = WpTarget::BruteForce.lines_in_file(wordlist)
lines.should == 6
end
end
describe '#brute_force' do
it 'gets the correct password' do
passwords = []
File.open(wordlist, 'r').each do |password|
# ignore comments
passwords << password.strip unless password.strip[0, 1] == '#'
end
# Last status must be 302 to get full code coverage
passwords.each do |password|
stub_request(:post, wp_target.login_url).
to_return(
{ status: 200, body: 'login_error' },
{ status: 0, body: 'no reponse' },
{ status: 500, body: 'server error' },
{ status: 999, body: 'invalid' },
{ status: 302, body: 'FOUND!' }
)
end
user = WpUser.new(wp_target.uri, login: 'admin')
result = wp_target.brute_force([user], wordlist)
result.length.should == 1
result.should === [{ name: 'admin', password: 'root' }]
end
it 'covers the timeout branch and return an empty array' do
stub_request(:post, wp_target.login_url).to_timeout
user = WpUser.new(wp_target.uri, login: 'admin')
result = wp_target.brute_force([user], wordlist)
result.should == []
end
end
end

View File

@@ -0,0 +1,53 @@
# encoding: UTF-8
shared_examples 'WpTarget::Malwares' do
let(:fixtures_dir) { SPEC_FIXTURES_WPSCAN_WP_TARGET_DIR + '/malwares' }
let(:malwares_file) { fixtures_dir + '/malwares.txt' }
describe '#malwares_file' do
it "returns the correct file path" do
WpTarget::Malwares.malwares_file(malwares_file).should === malwares_file
end
end
describe '#malwares & #has_malwares' do
after :each do
if @fixture
stub_request_to_fixture(url: wp_target.url, fixture: File.new(fixtures_dir + @fixture))
end
malwares = wp_target.malwares(@malwares_file_path)
malwares.sort.should === @expected.sort
wp_target.has_malwares?.should === (@expected.empty? ? false : true)
end
it 'returns an empty array on a 404' do
stub_request(:get, wp_target.url).to_return(status: 404)
@expected = []
end
it 'returns an array empty array if no infection found' do
@fixture = '/clean.html'
@expected = []
end
it 'returns an array with 1 malware url (.rr.nu check)' do
@fixture = '/single-infection.html'
@expected = ['http://irstde24clined.rr.nu/mm.php?d=1']
end
it 'returns an array with 1 malware url (iframe check)' do
@fixture = '/single-iframe-infection.html'
@expected = ['http://www.thesea.org/media.php']
end
it 'returns an array with 3 malwares url' do
@fixture = '/multiple-infections.html'
@expected = ['http://irstde24clined.rr.nu/mm.php?d=1', 'http://atio79srem.rr.nu/pmg.php?dr=1', 'http://www.thesea.org/media.php']
end
end
end

View File

@@ -0,0 +1,61 @@
# encoding: UTF-8
shared_examples 'WpTarget::WpConfigBackup' do
let(:fixtures_dir) { SPEC_FIXTURES_WPSCAN_WP_TARGET_DIR + '/wp_config_backup' }
let(:config_backup_files) { WpTarget::WpConfigBackup.config_backup_files }
describe '#config_backup' do
# set all @config_backup_files to point to a 404
before :each do
config_backup_files.each do |backup_file|
file_url = wp_target.uri.merge(URI.escape(backup_file)).to_s
stub_request(:get, file_url).to_return(status: 404)
end
end
it 'shoud return an empty array if no config backup is present' do
wp_target.config_backup.should be_empty
end
it 'returns an array with 1 backup file' do
expected = []
config_backup_files.sample(1).each do |backup_file|
file_url = wp_target.uri.merge(URI.escape(backup_file)).to_s
expected << file_url
stub_request_to_fixture(url: file_url, fixture: fixtures_dir + '/wp-config.php')
end
wp_config_backup = wp_target.config_backup
wp_config_backup.should_not be_empty
wp_config_backup.should === expected
end
# Is there a way to factorise that one with the previous test ?
it 'returns an array with 2 backup file' do
expected = []
config_backup_files.sample(2).each do |backup_file|
file_url = wp_target.uri.merge(URI.escape(backup_file)).to_s
expected << file_url
stub_request_to_fixture(url: file_url, fixture: fixtures_dir + '/wp-config.php')
end
wp_config_backup = wp_target.config_backup
wp_config_backup.should_not be_empty
wp_config_backup.sort.should === expected.sort
end
end
describe '#config_backup_files' do
it 'does not contain duplicates' do
config_backup_files.flatten.uniq.length.should == config_backup_files.length
end
end
end

View File

@@ -0,0 +1,143 @@
# encoding: UTF-8
shared_examples 'WpTarget::WpCustomDirectories' do
describe '#wp_content_dir' do
let(:fixtures_dir) { SPEC_FIXTURES_WPSCAN_WP_TARGET_DIR + '/wp_content_dir' }
after :each do
@wp_target = WpTarget.new(@target_url) if @target_url
stub_request_to_fixture(url: @wp_target.url, fixture: @fixture) if @fixture
stub_request(:get, /.*\/wp-content\/?$/).to_return(:status => 200, :body => '') # default dir request
stub_request(:get, /.*\.html$/).to_return(:status => 200, :body => '') # 404 hash request
@wp_target.wp_content_dir.should === @expected
end
it 'returns the string set in the initialize method' do
@wp_target = WpTarget.new('http://example.localhost/', options.merge(wp_content_dir: 'hello-world'))
@expected = 'hello-world'
end
it "returns 'wp-content'" do
@target_url = 'http://lamp/wordpress-3.4.1'
@fixture = fixtures_dir + '/wordpress-3.4.1.htm'
@expected = 'wp-content'
end
it "returns 'wp-content' if url has trailing slash" do
@target_url = 'http://lamp/wordpress-3.4.1/'
@fixture = fixtures_dir + '/wordpress-3.4.1.htm'
@expected = 'wp-content'
end
it "should find the default 'wp-content' dir even if the target_url is not the same (ie : the user supply an IP address and the url used in the code is a domain)" do
@target_url = 'http://192.168.1.103/wordpress-3.4.1/'
@fixture = fixtures_dir + '/wordpress-3.4.1.htm'
@expected = 'wp-content'
end
it "returns 'custom-content'" do
@target_url = 'http://lamp/wordpress-3.4.1-custom'
@fixture = fixtures_dir + '/wordpress-3.4.1-custom.htm'
@expected = 'custom-content'
end
it "returns 'custom content spaces'" do
@target_url = 'http://lamp/wordpress-3.4.1-custom'
@fixture = fixtures_dir + '/wordpress-3.4.1-custom-with-spaces.htm'
@expected = 'custom content spaces'
end
it "returns 'custom-dir/subdir/content'" do
@target_url = 'http://lamp/wordpress-3.4.1-custom'
@fixture = fixtures_dir + '/wordpress-3.4.1-custom-subdirectories.htm'
@expected = 'custom-dir/subdir/content'
end
it 'should also check in src attributes' do
@target_url = 'http://lamp/wordpress-3.4.1'
@fixture = fixtures_dir + '/wordpress-3.4.1-in-src.htm'
@expected = 'wp-content'
end
it 'should find the location even if the src or href goes in the plugins dir' do
@target_url = 'http://wordpress-3.4.1-in-plugins.htm'
@fixture = fixtures_dir + '/wordpress-3.4.1-in-plugins.htm'
@expected = 'wp-content'
end
it 'should not detect facebook.com as a custom wp-content directory' do
@target_url = 'http://lamp.localhost/'
@fixture = fixtures_dir + '/facebook-detection.htm'
@expected = nil
end
end
describe '#default_wp_content_dir_exists?' do
after :each do
@wp_target = WpTarget.new('http://lamp.localhost/')
stub_request(:get, @wp_target.url).to_return(:status => 200, :body => 'homepage') # homepage request
@wp_target.default_wp_content_dir_exists?.should === @expected
end
it 'returns false if wp-content returns an invalid response code' do
stub_request(:get, /.*\/wp-content\/?$/).to_return(:status => 404, :body => '') # default dir request
stub_request(:get, /.*\.html$/).to_return(:status => 404, :body => '') # 404 hash request
@expected = false
end
it 'returns false if wp-content and homepage have same bodies' do
stub_request(:get, /.*\/wp-content\/?$/).to_return(:status => 200, :body => 'homepage') # default dir request
stub_request(:get, /.*\.html$/).to_return(:status => 404, :body => '404!') # 404 hash request
@expected = false
end
it 'returns false if wp-content and 404 page have same bodies' do
stub_request(:get, /.*\/wp-content\/?$/).to_return(:status => 200, :body => '404!') # default dir request
stub_request(:get, /.*\.html$/).to_return(:status => 404, :body => '404!') # 404 hash request
@expected = false
end
it 'returns true if wp-content, 404 page and hoempage return different bodies' do
stub_request(:get, /.*\/wp-content\/?$/).to_return(:status => 200, :body => '') # default dir request
stub_request(:get, /.*\.html$/).to_return(:status => 200, :body => '404!') # 404 hash request
@expected = true
end
end
describe '#wp_plugins_dir' do
after :each do
@wp_target.wp_plugins_dir.should === @expected
end
it 'returns the string set in the initialize method' do
@wp_target = WpTarget.new('http://example.localhost/', options.merge(wp_content_dir: 'asdf', wp_plugins_dir: 'custom-plugins'))
@expected = 'custom-plugins'
end
it "returns 'custom/plugins'" do
@wp_target = WpTarget.new('http://example.localhost/', options.merge(wp_content_dir: 'custom', wp_plugins_dir: nil))
@expected = 'custom/plugins'
end
end
describe '#wp_plugins_dir_exists?' do
let(:wp_target) { WpTarget.new('http://example.localhost/', custom_options) }
let(:custom_options) { options.merge(wp_content_dir: 'asdf', wp_plugins_dir: 'custom-plugins') }
let(:url) { wp_target.uri.merge(wp_target.wp_plugins_dir).to_s }
it 'returns true' do
stub_request(:get, url).to_return(status: 200)
wp_target.wp_plugins_dir_exists?.should == true
end
it 'returns false' do
stub_request(:get, url).to_return(status: 404)
wp_target.wp_plugins_dir_exists?.should == false
end
end
end

View File

@@ -0,0 +1,37 @@
# encoding: UTF-8
shared_examples 'WpTarget::WpFullPathDisclosure' do
let(:fixtures_dir) { SPEC_FIXTURES_WPSCAN_WP_TARGET_DIR + '/wp_full_path_disclosure' }
describe '#full_path_disclosure_url' do
it 'returns http://example.localhost/wp-includes/rss-functions.php' do
wp_target.full_path_disclosure_url.should === 'http://example.localhost/wp-includes/rss-functions.php'
end
end
describe '#has_full_path_disclosure?' do
after do
stub_request(:get, wp_target.full_path_disclosure_url).
to_return(@stub)
wp_target.has_full_path_disclosure?.should === @expected
end
it 'returns false on a 404' do
@stub = { status: 404 }
@expected = false
end
it 'returns false if no fpd found (blank page for example)' do
@stub = { status: 200, body: '' }
@expected = false
end
it 'returns true' do
@stub = { status: 200, body: File.new(fixtures_dir + '/rss-functions-disclosure.php') }
@expected = true
end
end
end

View File

@@ -0,0 +1,89 @@
# encoding: UTF-8
shared_examples 'WpTarget::WpLoginProtection' do
let(:fixtures_dir) { SPEC_FIXTURES_WPSCAN_WP_TARGET_DIR + '/wp_login_protection' }
before { wp_target.stub(:wp_plugins_dir).and_return('wp-content/plugins') }
# It will test all protected methods has_.*_protection with each fixtures to be sure that
# there is not false positive : for example the login-lock must not be detected as login-lockdown
describe '#has_.*_protection?' do
pattern = WpTarget::WpLoginProtection::LOGIN_PROTECTION_METHOD_PATTERN
fixtures = %w{
wp-login-clean.php wp-login-login_lockdown.php wp-login-login_lock.php
wp-login-better_wp_security.php wp-login-simple_login_lockdown.php wp-login-login_security_solution.php
wp-login-limit_login_attempts.php wp-login-bluetrait_event_viewer.php
}
# For plugins which are detected from the existence of their directory into wp-content/plugins/ (or one of their file)
# and not from a regex into the login page
special_plugins = %w{better_wp_security simple_login_lockdown login_security_solution limit_login_attempts bluetrait_event_viewer}
after :each do
stub_request_to_fixture(url: login_url, fixture: @fixture)
# Stub all special plugins urls to a 404 except if it's the one we want
special_plugins.each do |special_plugin|
special_plugin_call_detection_symbol = :"has_#{special_plugin}_protection?"
special_plugin_call_url_symbol = :"#{special_plugin}_url"
status_code = (@symbol_to_call === special_plugin_call_detection_symbol and @expected === true) ? 200 : 404
stub_request(:get, wp_target.send(special_plugin_call_url_symbol).to_s).to_return(status: status_code)
end
wp_target.send(@symbol_to_call).should === @expected
end
self.protected_instance_methods.grep(pattern).each do |symbol_to_call|
plugin_name_from_symbol = symbol_to_call[pattern, 1].gsub('_', '-')
fixtures.each do |fixture|
plugin_name_from_fixture = fixture[/wp-login-(.*)\.php/i, 1].gsub('_', '-')
expected = plugin_name_from_fixture === plugin_name_from_symbol ? true : false
it "#{symbol_to_call} with #{fixture} returns #{expected}" do
@plugin_name = plugin_name_from_fixture
@fixture = fixtures_dir + '/' + fixture
@symbol_to_call = symbol_to_call
@expected = expected
end
end
end
end
# Factorise this with the code above ? :D
describe '#login_protection_plugin' do
after :each do
stub_request_to_fixture(url: login_url, fixture: @fixture)
stub_request(:get, wp_target.send(:better_wp_security_url).to_s).to_return(status: 404)
stub_request(:get, wp_target.send(:simple_login_lockdown_url).to_s).to_return(status: 404)
stub_request(:get, wp_target.send(:login_security_solution_url).to_s).to_return(status: 404)
stub_request(:get, wp_target.send(:limit_login_attempts_url).to_s).to_return(status: 404)
stub_request(:get, wp_target.send(:bluetrait_event_viewer_url).to_s).to_return(status: 404)
wp_target.login_protection_plugin().should == @plugin_expected
wp_target.has_login_protection?.should === @has_protection_expected
end
it 'returns nil if no protection is present' do
@fixture = fixtures_dir + '/wp-login-clean.php'
@plugin_expected = nil
@has_protection_expected = false
end
it 'returns a login-lockdown WpPlugin object' do
@fixture = fixtures_dir + '/wp-login-login_lockdown.php'
@plugin_expected = WpPlugin.new(wp_target.uri, name: 'login-lockdown')
@has_protection_expected = true
end
it 'returns a login-lock WpPlugin object' do
@fixture = fixtures_dir + '/wp-login-login_lock.php'
@plugin_expected = WpPlugin.new(wp_target.uri, name: 'login-lock')
@has_protection_expected = true
end
end
end

View File

@@ -0,0 +1,37 @@
# encoding: UTF-8
shared_examples 'WpTarget::WpReadme' do
let(:fixtures_dir) { SPEC_FIXTURES_WPSCAN_WP_TARGET_DIR + '/wp_readme' }
describe '#readme_url' do
it 'returns http://example.localhost/readme.html' do
wp_target.readme_url.should === "#{wp_target.uri}readme.html"
end
end
describe '#has_readme?' do
after do
stub_request(:get, wp_target.readme_url).to_return(@stub)
wp_target.has_readme?.should === @expected
end
it 'returns false on a 404' do
@stub = { status: 404 }
@expected = false
end
it 'returns true if it exists' do
@stub = { status: 200, body: File.new(fixtures_dir + '/readme-3.2.1.html') }
@expected = true
end
# http://code.google.com/p/wpscan/issues/detail?id=108
it 'returns true even if the readme.html is not in english' do
@stub = { status: 200, body: File.new(fixtures_dir + '/readme-3.3.2-fr.html') }
@expected = true
end
end
end

View File

@@ -0,0 +1,96 @@
# encoding: UTF-8
shared_examples 'WpTarget::WpRegistrable' do
let(:signup_url) { wp_target.uri.merge('wp-signup.php').to_s }
describe '#registration_url' do
after { wp_target.registration_url.should === @expected }
context 'when multisite' do
it 'returns the signup url' do
wp_target.stub(:multisite?).and_return(true)
@expected = signup_url
end
end
context 'when not multisite' do
it 'returns the login url with ?action=register' do
wp_target.stub(:multisite?).and_return(false)
@expected = login_url + '?action=register'
end
end
end
describe '#registration_enabled?' do
after do
wp_target.stub(:multisite?).and_return(multisite)
stub_request(:get, wp_target.registration_url.to_s).to_return(@stub)
wp_target.registration_enabled?.should === @expected
end
context 'when multisite' do
let(:multisite) { true }
it 'returns false (multisite)' do
@stub = { status: 302, headers: { 'Location' => 'wp-login.php?registration=disabled' } }
@expected = false
end
it 'returns true (multisite)' do
@stub = { status: 200, body: %{<form id="setupform" method="post" action="wp-signup.php">} }
@expected = true
end
end
context 'when not multisite' do
let(:multisite) { false }
it 'returns false (not multisite)' do
@stub = { status: 302, headers: { 'Location' => 'wp-login.php?registration=disabled' } }
@expected = false
end
it 'returns true (not multisite)' do
@stub = { status: 200, body: %{<form name="registerform" id="registerform" action="wp-login.php"} }
@expected = true
end
it 'returns false' do
@stub = { status: 500 }
@expected = false
end
end
end
describe '#multisite?' do
after do
stub_request(:get, signup_url).to_return(@stub)
wp_target.multisite?.should === @expected
end
it 'returns false' do
@stub = { status: 302, headers: { 'Location' => 'wp-login.php?action=register' } }
@expected = false
end
it 'returns true' do
@stub = { status: 302, headers: { 'Location' => 'http://example.localhost/wp-signup.php' } }
@expected = true
end
it 'returns true' do
@stub = { status: 200 }
@expected = true
end
it 'returns false' do
@stub = { status: 500 }
@expected = false
end
end
end