diff --git a/lib/wpscan/modules/brute_force.rb b/lib/wpscan/modules/brute_force.rb index 73062035..8f7c456a 100644 --- a/lib/wpscan/modules/brute_force.rb +++ b/lib/wpscan/modules/brute_force.rb @@ -24,16 +24,17 @@ module BruteForce hydra = Browser.instance.hydra number_of_passwords = BruteForce.lines_in_file(wordlist_path) login_url = login_url() + found = [] logins.each do |login| queue_count = 0 request_count = 0 password_found = false - File.open(wordlist_path, 'r').each do |password| + File.open(wordlist_path, "r").each do |password| # ignore file comments, but will miss passwords if they start with a hash... - next if password[0,1] == '#' + next if password[0,1] == "#" # keep a count of the amount of requests to be sent request_count += 1 @@ -61,18 +62,20 @@ module BruteForce puts "\nIncorrect username and/or password." if @verbose elsif response.code == 302 puts "\n [SUCCESS] Username : #{username} Password : #{password}\n" + found << { :name => username, :password => password } password_found = true elsif response.timed_out? puts "ERROR: Request timed out." elsif response.code == 0 puts "ERROR: No response from remote server. WAF/IPS?" - elsif response.code =~ /^50/ + # code is a fixnum, needs a string for regex + elsif response.code.to_s =~ /^50/ puts "ERROR: Server error, try reducing the number of threads." else puts "\nERROR: We recieved an unknown response for #{password}..." if @verbose - puts 'Code: ' + response.code.to_s - puts 'Body: ' + response.body + puts "Code: #{response.code.to_s}" + puts "Body: #{response.body}" puts end end @@ -102,7 +105,7 @@ module BruteForce # run all of the remaining requests hydra.run end - + found end # Counts the number of lines in the wordlist @@ -110,7 +113,7 @@ module BruteForce # wordlists, although bareable. def self.lines_in_file(file_path) lines = 0 - File.open(file_path, 'r').each { |line| lines += 1 } + File.open(file_path, 'r').each { || lines += 1 } lines end end diff --git a/spec/fixtures/wpscan/modules/bruteforce/wordlist.txt b/spec/fixtures/wpscan/modules/bruteforce/wordlist.txt new file mode 100644 index 00000000..d8e75315 --- /dev/null +++ b/spec/fixtures/wpscan/modules/bruteforce/wordlist.txt @@ -0,0 +1,6 @@ +password1 +password2 +pa55w0rd +# comment +admin +root \ No newline at end of file diff --git a/spec/lib/wpscan/modules/brute_force_spec.rb b/spec/lib/wpscan/modules/brute_force_spec.rb new file mode 100644 index 00000000..27f3d10a --- /dev/null +++ b/spec/lib/wpscan/modules/brute_force_spec.rb @@ -0,0 +1,72 @@ +#-- +# WPScan - WordPress Security Scanner +# Copyright (C) 2012 +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +#++ + +shared_examples_for "BruteForce" do + before :each do + @module = WpScanModuleSpec.new("http://example.localhost") + @target_url = @module.uri.to_s + @fixtures_dir = SPEC_FIXTURES_WPSCAN_MODULES_DIR + "/bruteforce" + @wordlist = @fixtures_dir + "/wordlist.txt" + @username = "admin" + + @module.extend(BruteForce) + @module.verbose = true + Browser.instance.max_threads = 1 + end + + describe "#lines_in_file" do + it "should return 6" do + lines = BruteForce.lines_in_file(@wordlist) + lines.should == 6 + end + end + + describe "#brute_force" do + before :each do + + end + + it "should get 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 || + stub_request(:any, @module.login_url).to_return( { :status => 200, :body => "login_error" }, + { :status => 0, :body => "no reponse" }, + { :status => 50, :body => "server error" }, + { :status => 999, :body => "invalid" }, + { :status => 302, :body => "FOUND!" }) + end + + user = WpUser.new("admin", 1, nil) + result = @module.brute_force([user], @wordlist) + result.length.should == 1 + result.should === [{ :name => "admin", :password => "root" }] + end + + it "should cover the timeout branch and return an empty array" do + stub_request(:any, @module.login_url).to_timeout + user = WpUser.new("admin", 1, nil) + result = @module.brute_force([user], @wordlist) + result.should == [] + end + end +end \ No newline at end of file diff --git a/spec/lib/wpscan/wp_target_spec.rb b/spec/lib/wpscan/wp_target_spec.rb index c58a432f..14f55d43 100644 --- a/spec/lib/wpscan/wp_target_spec.rb +++ b/spec/lib/wpscan/wp_target_spec.rb @@ -37,6 +37,7 @@ describe WpTarget do it_should_behave_like "WpFullPathDisclosure" it_should_behave_like "WpLoginProtection" it_should_behave_like "Malwares" + it_should_behave_like "BruteForce" it_should_behave_like "WpUsernames" it_should_behave_like "WpTimthumbs" it_should_behave_like "WpPlugins" diff --git a/spec/lib/wpscan/wpscan_helper.rb b/spec/lib/wpscan/wpscan_helper.rb index c5bcb661..7ca8e1a3 100644 --- a/spec/lib/wpscan/wpscan_helper.rb +++ b/spec/lib/wpscan/wpscan_helper.rb @@ -30,7 +30,7 @@ SPEC_FIXTURES_WPSCAN_WP_VERSION_DIR = SPEC_FIXTURES_WPSCAN_DIR + '/wp_version' class WpScanModuleSpec attr_reader :uri - attr_accessor :error_404_hash, :wp_content_dir + attr_accessor :error_404_hash, :wp_content_dir, :verbose def initialize(target_url) @uri = URI.parse(add_http_protocol(target_url))