CVE-2026-11988

LearnPress <= 4.3.9.1 - Insecure Direct Object Reference to Authenticated (Subscriber+) Sensitive Information Disclosure via 'userId' Parameter

mediumAuthorization Bypass Through User-Controlled Key
6.5
CVSS Score
6.5
CVSS Score
medium
Severity
4.4.0
Patched in
1d
Time to patch

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:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
High
Confidentiality
None
Integrity
None
Availability

Technical Details

Affected versions<=4.3.9.1
PublishedJune 30, 2026
Last updatedJuly 1, 2026
Affected pluginlearnpress

What Changed in the Fix

Changes introduced in v4.4.0

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# 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 userId to the ID of an Administrator or Instructor.

3. Code Flow (Inferred)

  1. 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.
  2. The handler function (e.g., get_course_progress) retrieves the userId parameter from the WP_REST_Request object.
  3. 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.
    }
    
  4. 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.

  1. Identify Trigger: The LearnPress scripts are generally enqueued on any page containing a LearnPress shortcode (e.g., [learn_press_profile]).
  2. 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]'
    
  3. Extraction:
    • Navigate to the newly created page as the Subscriber user.
    • Use browser_eval to extract the nonce from the global configuration object.
    • Target Variable: window.lpGlobalSettings.nonce (inferred) or window.lpData.nonce.

5. Exploitation Strategy

Step 1: Environment Setup

  1. Identify the Administrator's User ID (usually 1).
  2. Create a Course and enroll the Administrator in it.
  3. 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 OK response containing JSON data with keys like status, results, percentage, and finished_t.

6. Test Data Setup

  1. Target User (Admin): Ensure user with ID 1 exists.
  2. Attacker User (Subscriber):
    wp user create attacker attacker@example.com --role=subscriber --user_pass=password123
    
  3. 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);"
    
  4. 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 Forbidden response, 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:

  1. List Routes: http://localhost:8080/wp-json/lp/v1
  2. Search for userId: Scan the JSON response from the index for any route containing userId or user_id as a template parameter or argument.
  3. Profile Routes: Check /wp-json/lp/v1/profile/course-progress?userId=1. In many IDORs, endpoints designed for "My Profile" accidentally allow a userId override.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.