Trying to reduce the complexity of WpUser#brute_force

This commit is contained in:
erwanlr
2013-04-17 12:48:18 +02:00
parent 0c8c5e2928
commit 4c57a00660
4 changed files with 62 additions and 25 deletions

View File

@@ -21,6 +21,11 @@ class WpUser < WpItem
end end
end end
# @return [ String ]
def login_url
@uri.merge('wp-login.php').to_s
end
# @return [ String ] # @return [ String ]
def to_s def to_s
s = "#{id}" s = "#{id}"

View File

@@ -3,6 +3,16 @@
class WpUser < WpItem class WpUser < WpItem
module BruteForcable module BruteForcable
# Brute force the user with the wordlist supplied
#
# It can take a long time to queue 2 million requests,
# for that reason, we queue browser.max_threads, send browser.max_threads,
# queue browser.max_threads and so on.
#
# hydra.run only returns when it has recieved all of its, responses.
# This means that while we are waiting for browser.max_threads,
# responses, we are waiting...
#
# @param [ String, Array<String> ] wordlist The wordlist path # @param [ String, Array<String> ] wordlist The wordlist path
# @param [ Hash ] options # @param [ Hash ] options
# @option options [ Boolean ] :verbose # @option options [ Boolean ] :verbose
@@ -13,21 +23,12 @@ class WpUser < WpItem
browser = Browser.instance browser = Browser.instance
hydra = browser.hydra hydra = browser.hydra
passwords = BruteForcable.passwords_from_wordlist(wordlist) passwords = BruteForcable.passwords_from_wordlist(wordlist)
login = self.login
login_url = @uri.merge('wp-login.php').to_s
queue_count = 0 queue_count = 0
found = false found = false
progress_bar = ProgressBar.create(format: '%t %a <%B> (%c / %C) %P%% %e', progress_bar = self.progress_bar(passwords.size) if options[:show_progression]
title: " Brute Forcing '#{login}'",
length: 120,
total: passwords.size) if options[:show_progression]
passwords.each do |password| passwords.each do |password|
request = Browser.instance.forge_request(login_url, request = login_request(password)
method: :post,
body: { log: login, pwd: password },
cache_ttl: 0
)
request.on_complete do |response| request.on_complete do |response|
progress_bar.progress += 1 if options[:show_progression] && !found progress_bar.progress += 1 if options[:show_progression] && !found
@@ -35,20 +36,15 @@ class WpUser < WpItem
puts "\n Trying Username : #{login} Password : #{password}" if options[:verbose] puts "\n Trying Username : #{login} Password : #{password}" if options[:verbose]
if valid_password?(response, password, options) if valid_password?(response, password, options)
self.password = password
found = true found = true
return # Used as break self.password = password
return
end end
end end
hydra.queue(request) hydra.queue(request)
queue_count += 1 queue_count += 1
# it can take a long time to queue 2 million requests,
# for that reason, we queue @threads, send @threads, queue @threads and so on.
# hydra.run only returns when it has recieved all of its,
# responses. This means that while we are waiting for @threads,
# responses, we are waiting...
if queue_count >= browser.max_threads if queue_count >= browser.max_threads
hydra.run hydra.run
queue_count = 0 queue_count = 0
@@ -60,6 +56,31 @@ class WpUser < WpItem
hydra.run hydra.run
end end
# @param [ Integer ] password_size
#
# @return [ ProgressBar ]
# :nocov:
def progress_bar(passwords_size)
ProgressBar.create(
format: '%t %a <%B> (%c / %C) %P%% %e',
title: " Brute Forcing '#{login}'",
length: 120,
total: passwords_size
)
end
# :nocov:
# @param [ String ] password
#
# @return [ Typhoeus::Request ]
def login_request(password)
Browser.instance.forge_request(login_url,
method: :post,
body: { log: login, pwd: password },
cache_ttl: 0
)
end
# @param [ Typhoeus::Response ] response # @param [ Typhoeus::Response ] response
# @param [ String ] password # @param [ String ] password
# @param [ Hash ] options # @param [ Hash ] options
@@ -82,10 +103,13 @@ class WpUser < WpItem
else else
puts "\n " + red('ERROR:') + " We received an unknown response for #{password}..." if options[:show_progression] puts "\n " + red('ERROR:') + " We received an unknown response for #{password}..." if options[:show_progression]
# HACK to get the coverage :/ (otherwise some output is present in the rspec) # :nocov:
puts red(" Code: #{response.code}") if options[:verbose] if options[:verbose]
puts red(" Body: #{response.body}") if options[:verbose] puts red(" Code: #{response.code}")
puts if options[:verbose] puts red(" Body: #{response.body}")
puts
end
# :nocov:
end end
false false
end end

View File

@@ -34,6 +34,10 @@ describe WpUser do
end end
end end
describe '#login_url' do
its(:login_url) { should == 'http://example.com/wp-login.php' }
end
describe '#to_s' do describe '#to_s' do
after do after do
subject.id = 1 subject.id = 1

View File

@@ -5,7 +5,6 @@ shared_examples 'WpUser::BruteForcable' do
let(:wordlist_iso) { fixtures_dir + '/wordlist-iso-8859-1.txt' } let(:wordlist_iso) { fixtures_dir + '/wordlist-iso-8859-1.txt' }
let(:wordlist_utf8) { fixtures_dir + '/wordlist-utf-8.txt' } let(:wordlist_utf8) { fixtures_dir + '/wordlist-utf-8.txt' }
let(:mod) { WpUser::BruteForcable } let(:mod) { WpUser::BruteForcable }
let(:login_url) { uri.merge('wp-login.php').to_s }
before { Browser.instance.max_threads = 1 } before { Browser.instance.max_threads = 1 }
@@ -113,6 +112,11 @@ shared_examples 'WpUser::BruteForcable' do
end end
end end
# TODO
describe '#login_request' do
end
describe '#brute_force' do describe '#brute_force' do
let(:passwords) { %w{pass1 pass2 yolo kansei£Ô} } let(:passwords) { %w{pass1 pass2 yolo kansei£Ô} }
let(:login) { 'someuser' } let(:login) { 'someuser' }
@@ -125,7 +129,7 @@ shared_examples 'WpUser::BruteForcable' do
context 'when no password is valid' do context 'when no password is valid' do
before do before do
stub_request(:post, login_url). stub_request(:post, wp_user.login_url).
#with(body: { log: login }). # produces an error : undefined method `split' for {:log=>"someuser", :pwd=>"password1"}:Hash #with(body: { log: login }). # produces an error : undefined method `split' for {:log=>"someuser", :pwd=>"password1"}:Hash
to_return(body: 'login_error') to_return(body: 'login_error')
end end
@@ -139,7 +143,7 @@ shared_examples 'WpUser::BruteForcable' do
# Due to the error with .with(body: { log: login }) above # Due to the error with .with(body: { log: login }) above
# We can't use it to stub the request for a specific password # We can't use it to stub the request for a specific password
# So, the first one will be valid # So, the first one will be valid
before { stub_request(:post, login_url).to_return(status: 302) } before { stub_request(:post, wp_user.login_url).to_return(status: 302) }
it 'sets the @password' do it 'sets the @password' do
@expected = passwords[0] @expected = passwords[0]