CVE-2025-14802

LearnPress – WordPress LMS Plugin <= 4.3.2.2 - Insecure Direct Object Reference to Authenticated (Instructor+) Teacher Material Deletion

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

Description

The LearnPress – WordPress LMS Plugin for WordPress is vulnerable to unauthorized file deletion in versions up to, and including, 4.3.2.2 via the /wp-json/lp/v1/material/{file_id} REST API endpoint. This is due to a parameter mismatch between the DELETE operation and authorization check, where the endpoint uses file_id from the URL path but the permission callback validates item_id from the request body. This makes it possible for authenticated attackers, with teacher-level access, to delete arbitrary lesson material files uploaded by other teachers via sending a DELETE request with their own item_id (to pass authorization) while targeting another teacher's file_id.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
None
Confidentiality
Low
Integrity
Low
Availability

Technical Details

Affected versions<=4.3.2.1
PublishedJanuary 6, 2026
Last updatedJanuary 7, 2026
Affected pluginlearnpress

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2025-14802 (LearnPress IDOR Material Deletion) ## 1. Vulnerability Summary The LearnPress plugin (<= 4.3.2.1) contains an Insecure Direct Object Reference (IDOR) vulnerability in its REST API material management component. Specifically, the `DELETE` endpoint for le…

Show full research plan

Exploitation Research Plan: CVE-2025-14802 (LearnPress IDOR Material Deletion)

1. Vulnerability Summary

The LearnPress plugin (<= 4.3.2.1) contains an Insecure Direct Object Reference (IDOR) vulnerability in its REST API material management component. Specifically, the DELETE endpoint for lesson materials performs an authorization check based on an item_id (Lesson ID) provided in the request body/parameters, while the actual deletion operation targets a file_id provided in the URL path.

Because there is no verification that the file_id actually belongs to the item_id being authorized, an authenticated teacher can provide an item_id they own to pass the permission check, while specifying a file_id belonging to another teacher's course to delete it.

2. Attack Vector Analysis

  • Vulnerable Endpoint: /wp-json/lp/v1/material/{file_id}
  • HTTP Method: DELETE
  • Vulnerable Parameters:
    • file_id (URL Path): The ID of the material to be deleted.
    • item_id (Request Parameter): The ID of the lesson/item the material is supposedly associated with (used for the flawed permission check).
  • Authentication Required: Authenticated user with the lp_teacher (Instructor) role.
  • Preconditions:
    1. The attacker must have a valid lp_teacher account.
    2. The attacker must know or enumerate the file_id of the target material (Lesson Material IDs are typically auto-incrementing integers in the database).

3. Code Flow (Inferred)

  1. Registration: The plugin registers the route in inc/rest-api/v1/frontend/class-lp-rest-material-controller.php (inferred) using register_rest_route.
  2. Permission Callback: The permission_callback for the DELETE method retrieves item_id via $request->get_param('item_id'). It calls a function like learn_press_can_user_edit_item($item_id) or checks if the current user is the author of that specific lesson.
  3. Dispatch: If the check passes (because the attacker owns the item_id), the request is dispatched to the controller's delete method.
  4. Sink: The delete method retrieves the file_id from the URL path: $request['file_id']. It then calls a database deletion method (e.g., LP_Material_DB::getInstance()->delete_material($file_id)) without verifying that $file_id is linked to the validated $item_id.

4. Nonce Acquisition Strategy

The LearnPress REST API relies on the standard WordPress REST API nonce (wp_rest).

  1. Login: Authenticate as the "Attacker Teacher" (lp_teacher role).
  2. Navigate: Navigate to the WordPress Dashboard or a LearnPress-enabled page where the REST API environment is initialized.
  3. Extraction:
    • Use browser_navigate to http://localhost:8080/wp-admin/.
    • Use browser_eval to extract the nonce from the global wpApiSettings object.
    • JavaScript: window.wpApiSettings.nonce

5. Test Data Setup

  1. Roles: Ensure the LearnPress "Instructor" role is available.
  2. Victim Teacher (Teacher A):
    • Create user teacher_a with role lp_teacher.
    • As teacher_a, create a Course and a Lesson.
    • Upload a "Material" file to that lesson.
    • Identify the file_id of the material and the item_id of the lesson.
  3. Attacker Teacher (Teacher B):
    • Create user teacher_b with role lp_teacher.
    • As teacher_b, create a Course and a Lesson.
    • Identify the item_id of teacher_b's own lesson.

6. Exploitation Strategy

  1. Authenticate: Log in as teacher_b.
  2. Collect Nonce: Get the X-WP-Nonce as described in section 4.
  3. Execute IDOR: Send a DELETE request targeting teacher_a's material while authorizing with teacher_b's lesson ID.

HTTP Request (via http_request tool):

  • Method: DELETE
  • URL: http://localhost:8080/wp-json/lp/v1/material/{TEACHER_A_FILE_ID}?item_id={TEACHER_B_ITEM_ID}
  • Headers:
    • Content-Type: application/json
    • X-WP-Nonce: {EXTRACTED_NONCE}
    • Cookie: {TEACHER_B_COOKIES}

7. Expected Results

  • Response: The server should return a 200 OK or 204 No Content response, likely with a JSON body indicating success (e.g., {"status": "success"}).
  • Outcome: The record in the LearnPress material table corresponding to TEACHER_A_FILE_ID will be deleted from the database.

8. Verification Steps

  1. Check Database: Use WP-CLI to check if the material record still exists.
    • wp db query "SELECT * FROM wp_learnpress_materials WHERE exists (select * from wp_learnpress_materials where id = {TEACHER_A_FILE_ID})" (Note: Table name wp_learnpress_materials is inferred based on plugin naming conventions).
  2. Verify UI: Attempt to view the materials for Teacher A's lesson in the dashboard; the deleted material should no longer be listed.

9. Alternative Approaches

  • Request Body: If the item_id is not accepted as a query parameter for a DELETE request, try sending it in a JSON-encoded body: {"item_id": {TEACHER_B_ITEM_ID}}.
  • Method Tunneling: If the server restricts DELETE methods, try a POST request with the X-HTTP-Method-Override: DELETE header or the _method=DELETE query parameter targeting the same endpoint.
  • Batch Deletion (if supported): Check if the endpoint accepts an array of IDs, and attempt to mix own IDs with victim IDs.
Research Findings
Static analysis — not yet PoC-verified

Summary

The LearnPress plugin (<= 4.3.2.1) is vulnerable to an Insecure Direct Object Reference (IDOR) that allows authenticated instructors to delete materials belonging to other teachers. This occurs because the REST API authorization check validates the user's ownership of a provided Lesson ID (item_id) but performs the deletion on a File ID (file_id) provided in the URL without verifying the relationship between the two.

Vulnerable Code

/* File: inc/rest-api/v1/frontend/class-lp-rest-material-controller.php (inferred) */

// Permission callback checks item_id provided in the request body/params
public function check_admin_permission( $request ) {
    $item_id = $request->get_param( 'item_id' );
    return learn_press_can_user_edit_item( $item_id );
}

---

// Deletion handler uses file_id from the URL path
public function delete_material( $request ) {
    $file_id = $request['file_id'];
    return LP_Material_DB::getInstance()->delete_material( $file_id );
}

Security Fix

--- a/inc/rest-api/v1/frontend/class-lp-rest-material-controller.php
+++ b/inc/rest-api/v1/frontend/class-lp-rest-material-controller.php
@@ -100,5 +100,11 @@
     public function check_admin_permission( $request ) {
         $item_id = $request->get_param( 'item_id' );
-        return learn_press_can_user_edit_item( $item_id );
+        $file_id = $request['file_id'] ?? 0;
+        
+        if ( ! learn_press_can_user_edit_item( $item_id ) ) {
+            return false;
+        }
+
+        // Added check to ensure the material actually belongs to the item being authorized
+        return LP_Material_DB::getInstance()->check_material_belong_to_item( $file_id, $item_id );
     }

Exploit Outline

1. Authenticate to the WordPress site as a user with the 'Instructor' (lp_teacher) role. 2. Identify the `file_id` of a lesson material belonging to another instructor (e.g., through enumeration or database inspection). 3. Identify a Lesson ID (`item_id`) that the attacker's account has permission to edit (a lesson they created). 4. Obtain a valid WordPress REST API nonce (`X-WP-Nonce`) from the dashboard context. 5. Send a `DELETE` request to `/wp-json/lp/v1/material/{VICTIM_FILE_ID}?item_id={ATTACKER_ITEM_ID}`. 6. The plugin's permission callback will verify the attacker owns the `item_id`, and upon success, the controller will proceed to delete the record for the `VICTIM_FILE_ID`.

Check if your site is affected.

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