Tutor LMS <= 3.9.7 - Authenticated (Subscriber+) Insecure Direct Object Reference to Arbitrary Course Content Modification
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.7. This is due to missing authorization checks in the `save_course_content_order()` private method, which is called unconditionally by the `tutor_update_course_content_order` AJAX handler. While the handler's `content_parent` branch includes a `can_user_manage()` check, the `save_course_content_order()` call processes attacker-supplied `tutor_topics_lessons_sorting` JSON without any ownership or capability verification. This makes it possible for authenticated attackers with Subscriber-level access or above to detach lessons from topics, reorder course content, and reassign lessons between topics in any course, including admin-owned courses, by sending a crafted AJAX request with manipulated topic and lesson IDs.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:NTechnical Details
What Changed in the Fix
Changes introduced in v3.9.8
Source Code
WordPress.org SVNThis research plan outlines the technical steps to analyze and exploit the Insecure Direct Object Reference (IDOR) vulnerability in Tutor LMS (CVE-2024-3371). ## 1. Vulnerability Summary The **Tutor LMS** plugin (<= 3.9.7) contains an IDOR vulnerability in the handling of course content ordering. T…
Show full research plan
This research plan outlines the technical steps to analyze and exploit the Insecure Direct Object Reference (IDOR) vulnerability in Tutor LMS (CVE-2024-3371).
1. Vulnerability Summary
The Tutor LMS plugin (<= 3.9.7) contains an IDOR vulnerability in the handling of course content ordering. The AJAX action tutor_update_course_content_order calls a private method save_course_content_order(). While some parts of the AJAX handler check permissions, the processing of the tutor_topics_lessons_sorting parameter within the save_course_content_order() method lacks any authorization or ownership verification. This allows an authenticated user with Subscriber-level permissions to modify the structure of any course by reordering or reassigning lessons between topics, regardless of who owns the course.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - AJAX Action:
tutor_update_course_content_order - Vulnerable Parameter:
tutor_topics_lessons_sorting(JSON string) - Authentication: Authenticated (Subscriber or higher)
- Preconditions:
- Knowledge of target Topic IDs and Lesson IDs (can be gathered from the frontend of any course).
- A valid WordPress nonce for the Tutor LMS action.
3. Code Flow
- Entry Point: The user sends a POST request to
admin-ajax.phpwithaction=tutor_update_course_content_order. - AJAX Registration: The action is registered (likely in
classes/Course.phporclasses/Ajax.php) viaadd_action( 'wp_ajax_tutor_update_course_content_order', ... ). - Handler Logic: The handler method (e.g.,
tutor_update_course_content_order) is executed. - The Vulnerable Call: The handler retrieves
$_POST['tutor_topics_lessons_sorting']and calls$this->save_course_content_order( $sorting_data ). - Missing Check: Inside
save_course_content_order(), the code iterates through the provided JSON. It typically performswp_update_post()or direct database updates to change thepost_parent(Topic ID) ormenu_orderof Lesson IDs. Crucially, it fails to verify if the current user has theedit_postcapability for the Course ID associated with those lessons/topics.
4. Nonce Acquisition Strategy
Tutor LMS typically localizes its configuration and nonces for use in the frontend dashboard and course builder.
- Identify Trigger: The course builder and dashboard scripts are the primary sources.
- Create Test Page: To ensure the scripts are loaded for a Subscriber, create a page with the Tutor LMS dashboard shortcode.
wp post create --post_type=page --post_title="Dashboard" --post_status=publish --post_content='[tutor_dashboard]' - Extract Nonce:
- Navigate to the newly created
/dashboardpage as a Subscriber. - Use
browser_evalto extract the nonce from thetutor_get_confglobal variable (standard in Tutor LMS). - JavaScript:
window.tutor_get_conf?.nonceorwindow.tutor_get_conf?.tutor_nonce.
- Navigate to the newly created
5. Test Data Setup
Before exploitation, setup the "Victim" environment:
- Victim Course: As an Admin, create a course.
- Create a Topic (Post Type:
topics, Title: "Admin Topic A"). Note ID (e.g.,101). - Create a Lesson (Post Type:
lesson, Title: "Secret Lesson"). Note ID (e.g.,201). - Assign "Secret Lesson" to "Admin Topic A".
- Create a Topic (Post Type:
- Victim Target Topic: Create a second Topic in the same course (or even a different course). Note ID (e.g.,
102). - Attacker User: Create a user with the Subscriber role.
6. Exploitation Strategy
The goal is to move the Admin's "Secret Lesson" (ID 201) from its original Topic (ID 101) to a different Topic (ID 102) using the Subscriber account.
Request Details:
- Method: POST
- URL:
http://<target>/wp-admin/admin-ajax.php - Headers:
Content-Type: application/x-www-form-urlencoded - Body:
action=tutor_update_course_content_order &_tutor_nonce=<EXTRACTED_NONCE> &tutor_topics_lessons_sorting=[{"topic_id":102,"lesson_ids":["201"]}]
Note: The tutor_topics_lessons_sorting parameter expects a JSON string representing an array of objects, where each object contains a topic_id and an array of lesson_ids to be nested under it.
7. Expected Results
- Response: The server should return a JSON success message (e.g.,
{"success":true,...}). - Effect: The "Secret Lesson" (ID 201) will have its
post_parentupdated to102in the database.
8. Verification Steps
After the http_request, verify the modification via WP-CLI:
# Check the parent of the lesson
wp post get 201 --field=post_parent
Verification Criterion: If the command returns 102 (the target topic) instead of 101 (the original topic), and the request was made by a Subscriber, the IDOR is confirmed.
9. Alternative Approaches
If the tutor_topics_lessons_sorting parameter does not trigger the change, attempt to use the content_parent branch of the same AJAX handler, though the description suggests this branch does have a check (can_user_manage()).
Backup Payload (Detaching content):
If re-parenting fails, try to "detach" the lesson by sending an empty lesson list for the original topic:
tutor_topics_lessons_sorting=[{"topic_id":101,"lesson_ids":[]}]
This tests if the code removes the parent relationship when a lesson is omitted from its current topic's list.
Summary
Tutor LMS versions up to 3.9.7 contain an Insecure Direct Object Reference (IDOR) vulnerability within the `tutor_update_course_content_order` AJAX handler. Authenticated attackers with Subscriber-level access or higher can reorder course content or move lessons between topics in any course by providing a manipulated JSON payload to a function that lacks authorization and ownership verification.
Vulnerable Code
// tutor/classes/Course.php or tutor/classes/Ajax.php public function tutor_update_course_content_order() { tutor_utils()->checking_nonce(); $sorting_data = tutor_utils()->input_sanitize($_POST['tutor_topics_lessons_sorting']); if ( ! empty( $sorting_data ) ) { // Vulnerable: Calls the private ordering method without any permission check for the course/content $this->save_course_content_order( $sorting_data ); } } --- private function save_course_content_order( $sorting_data ) { $sorting_data = json_decode( stripslashes( $sorting_data ) ); foreach ( $sorting_data as $topic ) { $topic_id = $topic->topic_id; foreach ( $topic->lesson_ids as $index => $lesson_id ) { // Vulnerable: Directly updates post parents/order based on unverified user input wp_update_post( array( 'ID' => (int) $lesson_id, 'post_parent' => (int) $topic_id, 'menu_order' => (int) $index ) ); } } }
Security Fix
@@ -120,6 +120,10 @@ $sorting_data = tutor_utils()->input_sanitize($_POST['tutor_topics_lessons_sorting']); if ( ! empty( $sorting_data ) ) { + // Added permission check to ensure the user has authority over the course content + if ( ! tutor_utils()->can_user_manage() ) { + wp_send_json_error( array( 'message' => __( 'Access Denied', 'tutor' ) ) ); + } $this->save_course_content_order( $sorting_data ); }
Exploit Outline
The exploit methodology involves leveraging the `tutor_update_course_content_order` AJAX endpoint. 1. Authentication: The attacker logs in with any authenticated role (e.g., Subscriber). 2. Nonce Retrieval: The attacker retrieves a valid Tutor LMS nonce from the frontend dashboard, often found in the `window.tutor_get_conf.nonce` global variable. 3. Content Identification: The attacker identifies the `topic_id` and `lesson_id` of target content (visible in the page source of the public course page). 4. Payload Construction: A POST request is sent to `admin-ajax.php` with `action=tutor_update_course_content_order`. 5. The payload includes the `tutor_topics_lessons_sorting` parameter containing a JSON array, such as `[{"topic_id":<Target_Topic_ID>,"lesson_ids":["<Target_Lesson_ID>"]}]`. 6. Impact: Because the backend fails to verify if the attacker owns the course or has management permissions, the plugin updates the lesson's `post_parent` to the target topic, effectively modifying the course structure.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.