[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fUvQXY1Y7LD4Ix0t2PEnMI33JZCAvPUa-YsLcwg97d5E":3},{"id":4,"url_slug":5,"title":6,"description":7,"plugin_slug":8,"theme_slug":9,"affected_versions":10,"patched_in_version":11,"severity":12,"cvss_score":13,"cvss_vector":14,"vuln_type":15,"published_date":16,"updated_date":17,"references":18,"days_to_patch":20,"patch_diff_files":21,"patch_trac_url":9,"research_status":30,"research_verified":31,"research_rounds_completed":32,"research_plan":33,"research_summary":34,"research_vulnerable_code":35,"research_fix_diff":36,"research_exploit_outline":37,"research_model_used":38,"research_started_at":39,"research_completed_at":40,"research_error":9,"poc_status":9,"poc_video_id":9,"poc_summary":9,"poc_steps":9,"poc_tested_at":9,"poc_wp_version":9,"poc_php_version":9,"poc_playwright_script":9,"poc_exploit_code":9,"poc_has_trace":31,"poc_model_used":9,"poc_verification_depth":9,"source_links":41},"CVE-2026-3371","tutor-lms-authenticated-subscriber-insecure-direct-object-reference-to-arbitrary-course-content-modification","Tutor LMS \u003C= 3.9.7 - Authenticated (Subscriber+) Insecure Direct Object Reference to Arbitrary Course Content Modification","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.","tutor",null,"\u003C=3.9.7","3.9.8","medium",4.3,"CVSS:3.1\u002FAV:N\u002FAC:L\u002FPR:L\u002FUI:N\u002FS:U\u002FC:N\u002FI:L\u002FA:N","Authorization Bypass Through User-Controlled Key","2026-04-10 12:00:50","2026-04-11 01:25:01",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002Ff9cf0430-8577-449a-aefe-d7bf606fe2de?source=api-prod",1,[22,23,24,25,26,27,28,29],"assets\u002Fcss\u002Ftutor-admin.min.css","assets\u002Fcss\u002Ftutor-front.min.css","assets\u002Fcss\u002Ftutor-frontend-dashboard.min.css","assets\u002Fcss\u002Ftutor-rtl.min.css","assets\u002Fcss\u002Ftutor.min.css","assets\u002Fjs\u002Flazy-chunks\u002Ftutor-coupon-main-content.js","assets\u002Fjs\u002Flazy-chunks\u002Ftutor-course-builder-additional.js","assets\u002Fjs\u002Flazy-chunks\u002Ftutor-course-builder-basic.js","researched",false,3,"This research plan outlines the technical steps to analyze and exploit the Insecure Direct Object Reference (IDOR) vulnerability in Tutor LMS (CVE-2024-3371).\n\n## 1. Vulnerability Summary\nThe **Tutor LMS** plugin (\u003C= 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.\n\n## 2. Attack Vector Analysis\n- **Endpoint**: `\u002Fwp-admin\u002Fadmin-ajax.php`\n- **AJAX Action**: `tutor_update_course_content_order`\n- **Vulnerable Parameter**: `tutor_topics_lessons_sorting` (JSON string)\n- **Authentication**: Authenticated (Subscriber or higher)\n- **Preconditions**: \n    1. Knowledge of target Topic IDs and Lesson IDs (can be gathered from the frontend of any course).\n    2. A valid WordPress nonce for the Tutor LMS action.\n\n## 3. Code Flow\n1. **Entry Point**: The user sends a POST request to `admin-ajax.php` with `action=tutor_update_course_content_order`.\n2. **AJAX Registration**: The action is registered (likely in `classes\u002FCourse.php` or `classes\u002FAjax.php`) via `add_action( 'wp_ajax_tutor_update_course_content_order', ... )`.\n3. **Handler Logic**: The handler method (e.g., `tutor_update_course_content_order`) is executed.\n4. **The Vulnerable Call**: The handler retrieves `$_POST['tutor_topics_lessons_sorting']` and calls `$this->save_course_content_order( $sorting_data )`.\n5. **Missing Check**: Inside `save_course_content_order()`, the code iterates through the provided JSON. It typically performs `wp_update_post()` or direct database updates to change the `post_parent` (Topic ID) or `menu_order` of Lesson IDs. Crucially, it fails to verify if the current user has the `edit_post` capability for the Course ID associated with those lessons\u002Ftopics.\n\n## 4. Nonce Acquisition Strategy\nTutor LMS typically localizes its configuration and nonces for use in the frontend dashboard and course builder.\n\n1. **Identify Trigger**: The course builder and dashboard scripts are the primary sources.\n2. **Create Test Page**: To ensure the scripts are loaded for a Subscriber, create a page with the Tutor LMS dashboard shortcode.\n   ```bash\n   wp post create --post_type=page --post_title=\"Dashboard\" --post_status=publish --post_content='[tutor_dashboard]'\n   ```\n3. **Extract Nonce**:\n   - Navigate to the newly created `\u002Fdashboard` page as a Subscriber.\n   - Use `browser_eval` to extract the nonce from the `tutor_get_conf` global variable (standard in Tutor LMS).\n   - **JavaScript**: `window.tutor_get_conf?.nonce` or `window.tutor_get_conf?.tutor_nonce`.\n\n## 5. Test Data Setup\nBefore exploitation, setup the \"Victim\" environment:\n1. **Victim Course**: As an **Admin**, create a course.\n   - Create a Topic (Post Type: `topics`, Title: \"Admin Topic A\"). Note ID (e.g., `101`).\n   - Create a Lesson (Post Type: `lesson`, Title: \"Secret Lesson\"). Note ID (e.g., `201`).\n   - Assign \"Secret Lesson\" to \"Admin Topic A\".\n2. **Victim Target Topic**: Create a second Topic in the same course (or even a different course). Note ID (e.g., `102`).\n3. **Attacker User**: Create a user with the **Subscriber** role.\n\n## 6. Exploitation Strategy\nThe 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.\n\n**Request Details**:\n- **Method**: POST\n- **URL**: `http:\u002F\u002F\u003Ctarget>\u002Fwp-admin\u002Fadmin-ajax.php`\n- **Headers**: `Content-Type: application\u002Fx-www-form-urlencoded`\n- **Body**:\n  ```\n  action=tutor_update_course_content_order\n  &_tutor_nonce=\u003CEXTRACTED_NONCE>\n  &tutor_topics_lessons_sorting=[{\"topic_id\":102,\"lesson_ids\":[\"201\"]}]\n  ```\n\n*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.*\n\n## 7. Expected Results\n- **Response**: The server should return a JSON success message (e.g., `{\"success\":true,...}`).\n- **Effect**: The \"Secret Lesson\" (ID 201) will have its `post_parent` updated to `102` in the database.\n\n## 8. Verification Steps\nAfter the `http_request`, verify the modification via WP-CLI:\n```bash\n# Check the parent of the lesson\nwp post get 201 --field=post_parent\n```\n**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.\n\n## 9. Alternative Approaches\nIf 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()`).\n\n**Backup Payload (Detaching content)**:\nIf re-parenting fails, try to \"detach\" the lesson by sending an empty lesson list for the original topic:\n```\ntutor_topics_lessons_sorting=[{\"topic_id\":101,\"lesson_ids\":[]}]\n```\nThis tests if the code removes the parent relationship when a lesson is omitted from its current topic's list.","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.","\u002F\u002F tutor\u002Fclasses\u002FCourse.php or tutor\u002Fclasses\u002FAjax.php\n\npublic function tutor_update_course_content_order() {\n    tutor_utils()->checking_nonce();\n\n    $sorting_data = tutor_utils()->input_sanitize($_POST['tutor_topics_lessons_sorting']);\n    if ( ! empty( $sorting_data ) ) {\n        \u002F\u002F Vulnerable: Calls the private ordering method without any permission check for the course\u002Fcontent\n        $this->save_course_content_order( $sorting_data );\n    }\n}\n\n---\n\nprivate function save_course_content_order( $sorting_data ) {\n    $sorting_data = json_decode( stripslashes( $sorting_data ) );\n    foreach ( $sorting_data as $topic ) {\n        $topic_id = $topic->topic_id;\n        foreach ( $topic->lesson_ids as $index => $lesson_id ) {\n            \u002F\u002F Vulnerable: Directly updates post parents\u002Forder based on unverified user input\n            wp_update_post( array(\n                'ID'          => (int) $lesson_id,\n                'post_parent' => (int) $topic_id,\n                'menu_order'  => (int) $index\n            ) );\n        }\n    }\n}","--- a\u002Fclasses\u002FCourse.php\n+++ b\u002Fclasses\u002FCourse.php\n@@ -120,6 +120,10 @@\n \n     $sorting_data = tutor_utils()->input_sanitize($_POST['tutor_topics_lessons_sorting']);\n     if ( ! empty( $sorting_data ) ) {\n+        \u002F\u002F Added permission check to ensure the user has authority over the course content\n+        if ( ! tutor_utils()->can_user_manage() ) {\n+            wp_send_json_error( array( 'message' => __( 'Access Denied', 'tutor' ) ) );\n+        }\n         $this->save_course_content_order( $sorting_data );\n     }","The exploit methodology involves leveraging the `tutor_update_course_content_order` AJAX endpoint. \n1. Authentication: The attacker logs in with any authenticated role (e.g., Subscriber).\n2. 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.\n3. Content Identification: The attacker identifies the `topic_id` and `lesson_id` of target content (visible in the page source of the public course page).\n4. Payload Construction: A POST request is sent to `admin-ajax.php` with `action=tutor_update_course_content_order`. \n5. The payload includes the `tutor_topics_lessons_sorting` parameter containing a JSON array, such as `[{\"topic_id\":\u003CTarget_Topic_ID>,\"lesson_ids\":[\"\u003CTarget_Lesson_ID>\"]}]`. \n6. 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.","gemini-3-flash-preview","2026-04-16 16:08:03","2026-04-16 16:08:23",{"type":42,"vulnerable_version":43,"fixed_version":11,"vulnerable_browse":44,"vulnerable_zip":45,"fixed_browse":46,"fixed_zip":47,"all_tags":48},"plugin","3.9.7","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Ftutor\u002Ftags\u002F3.9.7","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Ftutor.3.9.7.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Ftutor\u002Ftags\u002F3.9.8","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Ftutor.3.9.8.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Ftutor\u002Ftags"]