Checks if the wp-login.php is available before attacking it - Fixes #1519

This commit is contained in:
erwanlr
2020-07-16 10:22:45 +02:00
parent 97c995b64c
commit ff574b046c
5 changed files with 75 additions and 19 deletions

View File

@@ -23,27 +23,32 @@ module WPScan
] ]
end end
def attack_opts
@attack_opts ||= {
show_progression: user_interaction?,
multicall_max_passwords: ParsedCli.multicall_max_passwords
}
end
def run def run
return unless ParsedCli.passwords return unless ParsedCli.passwords
begin
found = []
if user_interaction? if user_interaction?
output('@info', output('@info',
msg: "Performing password attack on #{attacker.titleize} against #{users.size} user/s") msg: "Performing password attack on #{attacker.titleize} against #{users.size} user/s")
end end
attack_opts = {
show_progression: user_interaction?,
multicall_max_passwords: ParsedCli.multicall_max_passwords
}
begin
found = []
attacker.attack(users, passwords(ParsedCli.passwords), attack_opts) do |user| attacker.attack(users, passwords(ParsedCli.passwords), attack_opts) do |user|
found << user found << user
attacker.progress_bar.log("[SUCCESS] - #{user.username} / #{user.password}") attacker.progress_bar.log("[SUCCESS] - #{user.username} / #{user.password}")
end end
rescue Error::NoLoginInterfaceDetected => e
# TODO: Maybe output that in JSON as well.
output('@notice', msg: e.to_s) if user_interaction?
ensure ensure
output('users', users: found) output('users', users: found)
end end
@@ -65,6 +70,8 @@ module WPScan
case ParsedCli.password_attack case ParsedCli.password_attack
when :wp_login when :wp_login
raise Error::NoLoginInterfaceDetected unless target.login_url
Finders::Passwords::WpLogin.new(target) Finders::Passwords::WpLogin.new(target)
when :xmlrpc when :xmlrpc
raise Error::XMLRPCNotDetected unless xmlrpc raise Error::XMLRPCNotDetected unless xmlrpc
@@ -100,8 +107,10 @@ module WPScan
else else
Finders::Passwords::XMLRPC.new(xmlrpc) Finders::Passwords::XMLRPC.new(xmlrpc)
end end
else elsif target.login_url
Finders::Passwords::WpLogin.new(target) Finders::Passwords::WpLogin.new(target)
else
raise Error::NoLoginInterfaceDetected
end end
end end

View File

@@ -29,5 +29,11 @@ module WPScan
' use the --scope option or make sure the --url value given is the correct one' ' use the --scope option or make sure the --url value given is the correct one'
end end
end end
class NoLoginInterfaceDetected < Standard
def to_s
'Could not find a login interface to perform the password attack against'
end
end
end end
end end

View File

@@ -139,15 +139,16 @@ module WPScan
# the first time the method is called, and the effective_url is then used # the first time the method is called, and the effective_url is then used
# if suitable, otherwise the default wp-login will be. # if suitable, otherwise the default wp-login will be.
# #
# @return [ String ] The URL to the login page # @return [ String, false ] The URL to the login page or false if not detected
def login_url def login_url
return @login_url if @login_url return @login_url unless @login_url.nil?
@login_url = url('wp-login.php') @login_url = url('wp-login.php') # TODO: url(ParsedCli.login_uri)
res = Browser.get_and_follow_location(@login_url) res = Browser.get_and_follow_location(@login_url)
@login_url = res.effective_url if res.effective_url =~ /wp-login\.php\z/i && in_scope?(res.effective_url) @login_url = res.effective_url if res.effective_url =~ /wp-login\.php\z/i && in_scope?(res.effective_url)
@login_url = false if res.code == 404
@login_url @login_url
end end

View File

@@ -107,18 +107,37 @@ describe WPScan::Controller::PasswordAttack do
end end
describe '#attacker' do describe '#attacker' do
before do
allow(controller.target).to receive(:sub_dir)
controller.target.instance_variable_set(:@login_url, nil)
end
context 'when --password-attack provided' do context 'when --password-attack provided' do
let(:cli_args) { "#{super()} --password-attack #{attack}" } let(:cli_args) { "#{super()} --password-attack #{attack}" }
context 'when wp-login' do context 'when wp-login' do
before { stub_request(:get, controller.target.url('wp-login.php')).to_return(status: status) }
let(:attack) { 'wp-login' } let(:attack) { 'wp-login' }
context 'when available' do
let(:status) { 200 }
it 'returns the correct object' do it 'returns the correct object' do
expect(controller.attacker).to be_a WPScan::Finders::Passwords::WpLogin expect(controller.attacker).to be_a WPScan::Finders::Passwords::WpLogin
expect(controller.attacker.target).to be_a WPScan::Target expect(controller.attacker.target).to be_a WPScan::Target
end end
end end
context 'when not available (404)' do
let(:status) { 404 }
it 'raises an error' do
expect { controller.attacker }.to raise_error(WPScan::Error::NoLoginInterfaceDetected)
end
end
end
context 'when xmlrpc' do context 'when xmlrpc' do
context 'when xmlrpc not detected on target' do context 'when xmlrpc not detected on target' do
before do before do
@@ -172,14 +191,29 @@ describe WPScan::Controller::PasswordAttack do
context 'when automatic detection' do context 'when automatic detection' do
context 'when xmlrpc_get_users_blogs_enabled? is false' do context 'when xmlrpc_get_users_blogs_enabled? is false' do
it 'returns the WpLogin' do before do
expect(controller).to receive(:xmlrpc_get_users_blogs_enabled?).and_return(false) expect(controller).to receive(:xmlrpc_get_users_blogs_enabled?).and_return(false)
stub_request(:get, controller.target.url('wp-login.php')).to_return(status: status)
end
context 'when wp-login available' do
let(:status) { 200 }
it 'returns the WpLogin' do
expect(controller.attacker).to be_a WPScan::Finders::Passwords::WpLogin expect(controller.attacker).to be_a WPScan::Finders::Passwords::WpLogin
expect(controller.attacker.target).to be_a WPScan::Target expect(controller.attacker.target).to be_a WPScan::Target
end end
end end
context 'when wp-login.php not available' do
let(:status) { 404 }
it 'raises an error' do
expect { controller.attacker }.to raise_error(WPScan::Error::NoLoginInterfaceDetected)
end
end
end
context 'when xmlrpc_get_users_blogs_enabled? is true' do context 'when xmlrpc_get_users_blogs_enabled? is true' do
before do before do
expect(controller).to receive(:xmlrpc_get_users_blogs_enabled?).and_return(true) expect(controller).to receive(:xmlrpc_get_users_blogs_enabled?).and_return(true)

View File

@@ -246,6 +246,12 @@ shared_examples WPScan::Target::Platform::WordPress do
its(:login_url) { should eql target.url('wp-login.php') } its(:login_url) { should eql target.url('wp-login.php') }
end end
context 'when a 404' do
before { stub_request(:get, target.url('wp-login.php')).to_return(status: 404) }
its(:login_url) { should eql false }
end
context 'when a redirection occured' do context 'when a redirection occured' do
before do before do
expect(WPScan::Browser).to receive(:get_and_follow_location) expect(WPScan::Browser).to receive(:get_and_follow_location)