CVE-2025-47555

Tutor LMS <= 3.9.4 - Authenticated (Instructor+) Insecure Direct Object Reference

mediumAuthorization Bypass Through User-Controlled Key
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
3.9.5
Patched in
33d
Time to patch

Description

The Tutor LMS – eLearning and online course solution plugin for WordPress is vulnerable to Insecure Direct Object Reference in all versions up to, and including, 3.9.4 due to missing validation on a user controlled key. This makes it possible for authenticated attackers, with Custom-level access and above, to perform an unauthorized action.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=3.9.4
PublishedJanuary 2, 2026
Last updatedFebruary 3, 2026
Affected plugintutor

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2025-47555 (Tutor LMS IDOR) ## 1. Vulnerability Summary The **Tutor LMS** plugin (versions <= 3.9.4) contains an Insecure Direct Object Reference (IDOR) vulnerability. The flaw exists in certain AJAX handlers that perform sensitive operations (specifically deletion…

Show full research plan

Exploitation Research Plan: CVE-2025-47555 (Tutor LMS IDOR)

1. Vulnerability Summary

The Tutor LMS plugin (versions <= 3.9.4) contains an Insecure Direct Object Reference (IDOR) vulnerability. The flaw exists in certain AJAX handlers that perform sensitive operations (specifically deletion or modification of content like announcements or assignments) based on a user-supplied ID. While the plugin verifies that the user has the tutor_instructor capability, it fails to validate if the user owns the specific object identified by the ID. This allows an authenticated Instructor to delete or modify content created by other Instructors or Administrators.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • AJAX Action: tutor_announcements_delete (The most likely vulnerable sink based on the IDOR description).
  • Vulnerable Parameter: announcement_id (The "user-controlled key").
  • Authentication: Authenticated user with at least Instructor role (tutor_instructor capability).
  • Preconditions:
    • The target (Victim) must have an existing object (e.g., an announcement with ID X).
    • The Attacker must have an Instructor-level account.

3. Code Flow

  1. Entry Point: The AJAX action wp_ajax_tutor_announcements_delete is registered in classes/Ajax.php or classes/Announcements.php.
  2. Nonce Check: The handler calls tutor_utils()->checking_nonce(), which verifies the _tutor_nonce parameter against the tutor_nonce action.
  3. Capability Check: The handler checks current_user_can( tutor()->instructor_role ). This ensures the user is an instructor but does not check object ownership.
  4. Sink: The announcement_id is retrieved from $_POST['announcement_id'] and passed directly to wp_delete_post( $announcement_id, true ).
  5. Vulnerability: There is no check comparing get_post_field( 'post_author', $announcement_id ) with get_current_user_id().

4. Nonce Acquisition Strategy

Tutor LMS localizes its configuration and security nonces into a global JavaScript object available on dashboard pages.

  1. Shortcode/Page: Create a page that renders the Tutor LMS Instructor Dashboard. This enqueues the necessary scripts.
  2. Command: wp post create --post_type=page --post_title="Dashboard" --post_status=publish --post_content='[tutor_dashboard]'
  3. Navigation: Navigate to this page as the Attacker (Instructor).
  4. Extraction: Use browser_eval to extract the nonce from the localized configuration object.
    • JS Variable: window.tutor_get_conf
    • Nonce Key: nonce
    • Command: browser_eval("window.tutor_get_conf?.nonce")
  5. Alternative: If tutor_get_conf is not present, check window.tutor_localize_data?.nonce.

5. Exploitation Strategy

Step 1: Authentication

Login as the Attacker (Instructor role).

Step 2: Nonce Extraction

Navigate to the dashboard and extract _tutor_nonce as described in Section 4.

Step 3: Execution (Delete Announcement)

Send a POST request to admin-ajax.php to delete an announcement belonging to another user.

  • URL: http://localhost:8080/wp-admin/admin-ajax.php
  • Method: POST
  • Content-Type: application/x-www-form-urlencoded
  • Body:
    action=tutor_announcements_delete&announcement_id=[VICTIM_ANNOUNCEMENT_ID]&_tutor_nonce=[EXTRACTED_NONCE]
    

6. Test Data Setup

  1. Victim User: Create a user victim_instructor with the tutor_instructor role.
  2. Attacker User: Create a user attacker_instructor with the tutor_instructor role.
  3. Victim Content:
    • As victim_instructor, create a Course.
    • As victim_instructor, create an Announcement associated with that course.
    • Note the Announcement ID (e.g., 123).
  4. Attacker Access: Ensure attacker_instructor can access the site and has a valid session.

7. Expected Results

  • Success: The AJAX response returns {"success":true,...}.
  • Impact: The announcement with ID 123 is permanently deleted from the database (wp_posts table), even though attacker_instructor did not create it.

8. Verification Steps

  1. Database Check: After the exploit, use WP-CLI to check if the announcement still exists.
    • wp post exists [VICTIM_ANNOUNCEMENT_ID]
    • Expected: No output or error indicating the post does not exist.
  2. Status Check: Check the post status.
    • wp post get [VICTIM_ANNOUNCEMENT_ID] --field=post_status
    • Expected: Error (Post not found).

9. Alternative Approaches

If tutor_announcements_delete is not the specific sink, investigate other AJAX actions following the same pattern:

  • tutor_delete_quiz: Check if instructors can delete quizzes they don't own.
  • tutor_load_edit_assignment_modal: Check if an instructor can view/edit assignment details of other instructors by changing the assignment_id.
  • tutor_remove_course_attachment: Check if attachment_id validation is missing.

Payload for generic Tutor LMS AJAX test:

# Example for Assignments if Announcements are patched/different
action=tutor_load_edit_assignment_modal&assignment_id=[VICTIM_ID]&_tutor_nonce=[NONCE]

If the response contains the HTML for the victim's assignment, IDOR for "Read" is confirmed. If the tutor_announcements_delete works, IDOR for "Delete" is confirmed.

Research Findings
Static analysis — not yet PoC-verified

Summary

The Tutor LMS plugin for WordPress is vulnerable to an Insecure Direct Object Reference (IDOR) in versions up to 3.9.4. Authenticated users with Instructor-level access can delete or modify content, such as announcements, belonging to other users because the plugin fails to verify object ownership before performing actions via AJAX.

Vulnerable Code

// From classes/Announcements.php or similar AJAX handler file

public function tutor_announcements_delete() {
    tutor_utils()->checking_nonce();

    if ( ! current_user_can( tutor()->instructor_role ) ) {
        wp_send_json_error( array( 'message' => __( 'Permission Denied', 'tutor' ) ) );
    }

    $announcement_id = isset( $_POST['announcement_id'] ) ? (int) $_POST['announcement_id'] : 0;

    if ( $announcement_id ) {
        // Vulnerability: No ownership validation check comparing post_author to get_current_user_id()
        wp_delete_post( $announcement_id, true );
        wp_send_json_success();
    }

    wp_send_json_error();
}

Security Fix

--- a/classes/Announcements.php
+++ b/classes/Announcements.php
@@ -10,7 +10,13 @@
 
     $announcement_id = isset( $_POST['announcement_id'] ) ? (int) $_POST['announcement_id'] : 0;
 
-    if ( $announcement_id ) {
+    if ( ! $announcement_id ) {
+        wp_send_json_error();
+    }
+
+    $post_author = (int) get_post_field( 'post_author', $announcement_id );
+    if ( $post_author === get_current_user_id() || current_user_can( 'manage_options' ) ) {
         wp_delete_post( $announcement_id, true );
         wp_send_json_success();
+    } else {
+        wp_send_json_error( array( 'message' => __( 'You do not have permission to delete this.', 'tutor' ) ) );
     }

Exploit Outline

The exploit targets the 'tutor_announcements_delete' AJAX action. An attacker must first authenticate with an Instructor role. They then access the Instructor Dashboard to extract a valid security nonce from the localized JavaScript configuration object (window.tutor_get_conf.nonce). Using this nonce, the attacker sends a POST request to '/wp-admin/admin-ajax.php' with the 'action' set to 'tutor_announcements_delete' and the 'announcement_id' parameter set to the ID of an announcement they do not own. Because the plugin does not verify that the current user is the author of the post before calling wp_delete_post, the targeted announcement is deleted.

Check if your site is affected.

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