LearnPress <= 4.3.9.1 - Insecure Direct Object Reference to Authenticated (Subscriber+) Sensitive Information Disclosure via 'userId' Parameter
Description
The LearnPress – WordPress LMS Plugin for Create and Sell Online Courses plugin for WordPress is vulnerable to Insecure Direct Object Reference in all versions up to, and including, 4.3.9.1 via the 'userId' parameter due to missing validation on a user controlled key. This makes it possible for authenticated attackers, with subscriber-level access and above, to view the course enrollment progress and completion data belonging to any instructor or administrator account on the site. This IDOR does not apply when the target user is a regular subscriber, as the guard correctly blocks cross-subscriber access; exploitation is limited to cases where the victim user holds the LP_TEACHER_ROLE or administrator role.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:NTechnical Details
What Changed in the Fix
Changes introduced in v4.4.0
Source Code
WordPress.org SVN# Exploitation Research Plan - CVE-2026-11988 ## 1. Vulnerability Summary **CVE-2026-11988** is an Insecure Direct Object Reference (IDOR) vulnerability in the **LearnPress** plugin (versions <= 4.3.9.1). The vulnerability exists in a REST API or AJAX endpoint that processes course enrollment and p…
Show full research plan
Exploitation Research Plan - CVE-2026-11988
1. Vulnerability Summary
CVE-2026-11988 is an Insecure Direct Object Reference (IDOR) vulnerability in the LearnPress plugin (versions <= 4.3.9.1). The vulnerability exists in a REST API or AJAX endpoint that processes course enrollment and progress data. Due to an incomplete authorization guard, an authenticated user (Subscriber level) can access the sensitive course progress and completion details of Administrators and Instructors by manipulating the userId parameter.
The flaw is unique because the plugin implements a partial access control check: it prevents Subscribers from viewing other Subscribers' data, but fails to apply this restriction when the target user (the userId provided) holds an elevated role like administrator or lp_teacher.
2. Attack Vector Analysis
- Endpoint:
/wp-json/lp/v1/users/get-course-progress(inferred based on LearnPress 4.x REST API patterns) or a similar user-progress endpoint. - HTTP Method:
GET - Vulnerable Parameter:
userId - Authentication: Required (Subscriber level or higher).
- Required Headers:
X-WP-Nonce(standard WordPress REST API nonce). - Payload: Setting
userIdto the ID of an Administrator or Instructor.
3. Code Flow (Inferred)
- The request is routed to the REST controller responsible for user statistics, likely within
inc/rest-api/v1/frontend/class-lp-rest-users-controller.php. - The handler function (e.g.,
get_course_progress) retrieves theuserIdparameter from theWP_REST_Requestobject. - An authorization check is performed. The logic likely resembles:
$target_user_id = $request->get_param('userId'); $current_user_id = get_current_user_id(); if ($target_user_id !== $current_user_id) { $target_user = get_userdata($target_user_id); // FLURRED LOGIC: Only blocks if the target is a subscriber if (in_array('subscriber', $target_user->roles)) { return new WP_Error('rest_forbidden', 'You cannot view other students progress.', ['status' => 403]); } // If the target is an Admin or Teacher, the check falls through, granting access. } - The sensitive enrollment data is queried and returned in the JSON response.
4. Nonce Acquisition Strategy
The LearnPress REST API requires a valid WordPress REST nonce (wp_rest). LearnPress typically localizes its configuration data into the lpGlobalSettings JavaScript object.
- Identify Trigger: The LearnPress scripts are generally enqueued on any page containing a LearnPress shortcode (e.g.,
[learn_press_profile]). - Setup: Create a public profile page to ensure scripts load.
wp post create --post_type=page --post_title="Profile" --post_status=publish --post_content='[learn_press_profile]' - Extraction:
- Navigate to the newly created page as the Subscriber user.
- Use
browser_evalto extract the nonce from the global configuration object. - Target Variable:
window.lpGlobalSettings.nonce(inferred) orwindow.lpData.nonce.
5. Exploitation Strategy
Step 1: Environment Setup
- Identify the Administrator's User ID (usually
1). - Create a Course and enroll the Administrator in it.
- Note the Course ID (e.g.,
10).
Step 2: Request Construction
Using the http_request tool, perform the following call:
- URL:
http://localhost:8080/wp-json/lp/v1/users/get-course-progress?userId=1&courseId=10 - Method:
GET - Headers:
X-WP-Nonce:[EXTRACTED_NONCE]Cookie:[SUBSCRIBER_COOKIES]
- Expected Success: A
200 OKresponse containing JSON data with keys likestatus,results,percentage, andfinished_t.
6. Test Data Setup
- Target User (Admin): Ensure user with ID
1exists. - Attacker User (Subscriber):
wp user create attacker attacker@example.com --role=subscriber --user_pass=password123 - Course Data:
# Create a course COURSE_ID=$(wp post create --post_type=lp_course --post_title="Sensitive Course" --post_status=publish --porcelain) # Enroll the Admin in the course (requires LP database manipulation or standard enrollment) wp eval "learn_press_get_user(1)->enroll($COURSE_ID);" - Nonce Page:
wp post create --post_type=page --post_title="LP Nonce" --post_status=publish --post_content='[learn_press_profile]'
7. Expected Results
- Successful Exploit: The response contains the Administrator's progress data for the specified course (e.g.,
"status":"finished","percentage":100). - Blocked Attempt (Control): Attempting to access another Subscriber's ID should result in a
403 Forbiddenresponse, confirming the "guard" works for the same-level role but fails for elevated roles.
8. Verification Steps
After the HTTP request, verify the data returned matches the actual state of the Admin user:
# Check the enrollment status in the database via WP-CLI
wp eval "echo json_encode(learn_press_get_user(1)->get_course_data($COURSE_ID)->get_status());"
Compare the output of the command above with the status field in the JSON response from the exploit.
9. Alternative Approaches
If /wp-json/lp/v1/users/get-course-progress is not the correct endpoint, investigate other REST routes registered by the plugin:
- List Routes:
http://localhost:8080/wp-json/lp/v1 - Search for
userId: Scan the JSON response from the index for any route containinguserIdoruser_idas a template parameter or argument. - Profile Routes: Check
/wp-json/lp/v1/profile/course-progress?userId=1. In many IDORs, endpoints designed for "My Profile" accidentally allow auserIdoverride.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.