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

View File

@@ -107,15 +107,34 @@ describe WPScan::Controller::PasswordAttack do
end
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
let(:cli_args) { "#{super()} --password-attack #{attack}" }
context 'when wp-login' do
before { stub_request(:get, controller.target.url('wp-login.php')).to_return(status: status) }
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
context 'when available' do
let(:status) { 200 }
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 not available (404)' do
let(:status) { 404 }
it 'raises an error' do
expect { controller.attacker }.to raise_error(WPScan::Error::NoLoginInterfaceDetected)
end
end
end
@@ -172,11 +191,26 @@ describe WPScan::Controller::PasswordAttack do
context 'when automatic detection' 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)
stub_request(:get, controller.target.url('wp-login.php')).to_return(status: status)
end
expect(controller.attacker).to be_a WPScan::Finders::Passwords::WpLogin
expect(controller.attacker.target).to be_a WPScan::Target
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.target).to be_a WPScan::Target
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

View File

@@ -246,6 +246,12 @@ shared_examples WPScan::Target::Platform::WordPress do
its(:login_url) { should eql target.url('wp-login.php') }
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
before do
expect(WPScan::Browser).to receive(:get_and_follow_location)