LearnPress – WordPress LMS Plugin <= 4.3.2.2 - Insecure Direct Object Reference to Authenticated (Instructor+) Teacher Material Deletion
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:LTechnical Details
<=4.3.2.1Source Code
WordPress.org SVN# 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:
- The attacker must have a valid
lp_teacheraccount. - The attacker must know or enumerate the
file_idof the target material (Lesson Material IDs are typically auto-incrementing integers in the database).
- The attacker must have a valid
3. Code Flow (Inferred)
- Registration: The plugin registers the route in
inc/rest-api/v1/frontend/class-lp-rest-material-controller.php(inferred) usingregister_rest_route. - Permission Callback: The
permission_callbackfor theDELETEmethod retrievesitem_idvia$request->get_param('item_id'). It calls a function likelearn_press_can_user_edit_item($item_id)or checks if the current user is the author of that specific lesson. - Dispatch: If the check passes (because the attacker owns the
item_id), the request is dispatched to the controller's delete method. - Sink: The delete method retrieves the
file_idfrom 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_idis linked to the validated$item_id.
4. Nonce Acquisition Strategy
The LearnPress REST API relies on the standard WordPress REST API nonce (wp_rest).
- Login: Authenticate as the "Attacker Teacher" (
lp_teacherrole). - Navigate: Navigate to the WordPress Dashboard or a LearnPress-enabled page where the REST API environment is initialized.
- Extraction:
- Use
browser_navigatetohttp://localhost:8080/wp-admin/. - Use
browser_evalto extract the nonce from the globalwpApiSettingsobject. - JavaScript:
window.wpApiSettings.nonce
- Use
5. Test Data Setup
- Roles: Ensure the LearnPress "Instructor" role is available.
- Victim Teacher (Teacher A):
- Create user
teacher_awith rolelp_teacher. - As
teacher_a, create a Course and a Lesson. - Upload a "Material" file to that lesson.
- Identify the
file_idof the material and theitem_idof the lesson.
- Create user
- Attacker Teacher (Teacher B):
- Create user
teacher_bwith rolelp_teacher. - As
teacher_b, create a Course and a Lesson. - Identify the
item_idofteacher_b's own lesson.
- Create user
6. Exploitation Strategy
- Authenticate: Log in as
teacher_b. - Collect Nonce: Get the
X-WP-Nonceas described in section 4. - Execute IDOR: Send a
DELETErequest targetingteacher_a's material while authorizing withteacher_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/jsonX-WP-Nonce: {EXTRACTED_NONCE}Cookie: {TEACHER_B_COOKIES}
7. Expected Results
- Response: The server should return a
200 OKor204 No Contentresponse, likely with a JSON body indicating success (e.g.,{"status": "success"}). - Outcome: The record in the LearnPress material table corresponding to
TEACHER_A_FILE_IDwill be deleted from the database.
8. Verification Steps
- 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 namewp_learnpress_materialsis inferred based on plugin naming conventions).
- 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_idis not accepted as a query parameter for aDELETErequest, try sending it in a JSON-encoded body:{"item_id": {TEACHER_B_ITEM_ID}}. - Method Tunneling: If the server restricts
DELETEmethods, try aPOSTrequest with theX-HTTP-Method-Override: DELETEheader or the_method=DELETEquery 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.
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
@@ -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.