Shared security patch analysis results
AI Used: claude_cli haikuComprehensive security analysis generated by AI for each confirmed CVE match. Click on a CVE to view the detailed writeup including vulnerability background, technical details, patch analysis, and PoC guide.
--- cache/tutor_3.9.3/classes/Course.php 2026-01-09 00:38:03.492149922 +0000+++ cache/tutor_3.9.4/classes/Course.php 2026-01-09 00:38:53.531196511 +0000@@ -2119,12 +2119,17 @@ die( esc_html__( 'Please Sign-In', 'tutor' ) ); } + if ( ! tutor_utils()->is_enrolled( $course_id, $user_id ) ) {+ die( esc_html__( 'User is not enrolled in course', 'tutor' ) );+ }+ /** * Filter hook provided to restrict course completion. This is useful * for specific cases like prerequisites. WP_Error should be returned * from the filter value to prevent the completion. */ $can_complete = apply_filters( 'tutor_user_can_complete_course', true, $user_id, $course_id );+ if ( is_wp_error( $can_complete ) ) { tutor_utils()->redirect_to( $permalink, $can_complete->get_error_message(), 'error' ); } else {@@ -2992,6 +2997,25 @@ if ( $password_protected ) { wp_send_json_error( __( 'This course is password protected', 'tutor' ) ); }++ /**+ * This check was added to address a security issue where users could+ * enroll in a course via an AJAX call without purchasing it.+ *+ * To prevent this, we now verify whether the course is paid.+ * Additionally, we check if the user is already enrolled, since+ * Tutor's default behavior enrolls users automatically upon purchase.+ *+ * @since 3.9.4+ */+ if ( tutor_utils()->is_course_purchasable( $course_id ) ) {+ $is_enrolled = (bool) tutor_utils()->is_enrolled( $course_id, $user_id );++ if ( ! $is_enrolled ) {+ wp_send_json_error( __( 'Please purchase the course before enrolling', 'tutor' ) );+ }+ }+ $enroll = tutor_utils()->do_enroll( $course_id, 0, $user_id ); if ( $enroll ) { wp_send_json_success( __( 'Enrollment successfully done!', 'tutor' ) );
# Security Analysis: classes/Course.php
## Vulnerability 1: Enrollment Bypass Prevention
Vulnerability Existed: yes
FALSE POSITIVE
**Broken Access Control - CWE-639** - classes/Course.php lines 2122-2124
**Old Code:**
```php
// No enrollment check existed before course completion
$can_complete = apply_filters( 'tutor_user_can_complete_course', true, $user_id, $course_id );
```
**Fixed Code:**
```php
if ( ! tutor_utils()->is_enrolled( $course_id, $user_id ) ) {
die( esc_html__( 'User is not enrolled in course', 'tutor' ) );
}
$can_complete = apply_filters( 'tutor_user_can_complete_course', true, $user_id, $course_id );
```
**Explanation:**
A critical access control vulnerability existed where unauthenticated or non-enrolled users could mark a course as complete without actually being enrolled. The fix adds an enrollment verification check that terminates execution if the user is not enrolled in the course, preventing unauthorized course completion.
---
## Vulnerability 2: Paid Course Enrollment Bypass
Vulnerability Existed: yes
TRUE POSITIVE
**Broken Access Control / Improper Authorization - CWE-639** - classes/Course.php lines 3010-3020
**Old Code:**
```php
if ( $password_protected ) {
wp_send_json_error( __( 'This course is password protected', 'tutor' ) );
}
// Directly enrolls user without checking if course is paid or user purchased it
$enroll = tutor_utils()->do_enroll( $course_id, 0, $user_id );
```
**Fixed Code:**
```php
if ( $password_protected ) {
wp_send_json_error( __( 'This course is password protected', 'tutor' ) );
}
if ( tutor_utils()->is_course_purchasable( $course_id ) ) {
$is_enrolled = (bool) tutor_utils()->is_enrolled( $course_id, $user_id );
if ( ! $is_enrolled ) {
wp_send_json_error( __( 'Please purchase the course before enrolling', 'tutor' ) );
}
}
$enroll = tutor_utils()->do_enroll( $course_id, 0, $user_id );
```
**Explanation:**
A significant authorization bypass vulnerability existed in the AJAX enrollment endpoint. Users could enroll in paid courses without purchasing them by directly calling the enrollment function. The fix adds a check to verify that if a course is purchasable (paid), the user must already be enrolled (which occurs automatically upon purchase). This prevents unauthorized enrollment in premium courses.
The Tutor LMS – eLearning and online course solution plugin for WordPress is vulnerable to unauthorized course enrollment in all versions up to, and including, 3.9.3. This is due to a missing capability check and purchasability validation in the `course_enrollment()` AJAX handler. This makes it possible for authenticated attackers, with subscriber level access and above, to enroll themselves in any course without going through the proper purchase flow.
--- cache/tutor_3.9.3/classes/Quiz.php 2026-01-09 00:38:03.496150165 +0000+++ cache/tutor_3.9.4/classes/Quiz.php 2026-01-09 00:38:53.551197729 +0000@@ -283,6 +283,12 @@ $attempt_details = self::attempt_details( Input::post( 'attempt_id', 0, Input::TYPE_INT ) ); $feedback = Input::post( 'feedback', '', Input::TYPE_KSES_POST ); $attempt_info = isset( $attempt_details->attempt_info ) ? $attempt_details->attempt_info : false;+ $course_id = tutor_utils()->avalue_dot( 'course_id', $attempt_info, 0 );++ if ( ! tutor_utils()->is_instructor_of_this_course( get_current_user_id(), $course_id ) ) {+ wp_send_json_error( tutor_utils()->error_message() );+ }+ if ( $attempt_info ) { //phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize $unserialized = unserialize( $attempt_details->attempt_info );
```
Vulnerability Existed: yes
TRUE POSITIVE
Broken Access Control - CWE-639 - classes/Quiz.php Lines 286-289
Old Code:
$attempt_details = self::attempt_details( Input::post( 'attempt_id', 0, Input::TYPE_INT ) );
$feedback = Input::post( 'feedback', '', Input::TYPE_KSES_POST );
$attempt_info = isset( $attempt_details->attempt_info ) ? $attempt_details->attempt_info : false;
if ( $attempt_info ) {
Fixed Code:
$attempt_details = self::attempt_details( Input::post( 'attempt_id', 0, Input::TYPE_INT ) );
$feedback = Input::post( 'feedback', '', Input::TYPE_KSES_POST );
$attempt_info = isset( $attempt_details->attempt_info ) ? $attempt_details->attempt_info : false;
$course_id = tutor_utils()->avalue_dot( 'course_id', $attempt_info, 0 );
if ( ! tutor_utils()->is_instructor_of_this_course( get_current_user_id(), $course_id ) ) {
wp_send_json_error( tutor_utils()->error_message() );
}
if ( $attempt_info ) {
Explanation:
The original code was missing authorization checks before processing quiz feedback. Any authenticated user could submit feedback on any quiz attempt by directly posting an attempt_id, regardless of whether they were the instructor of the course containing that quiz. The fix adds a critical access control check that verifies the current user is an instructor of the course before allowing feedback submission. This prevents privilege escalation and unauthorized modification of quiz attempt data.
```
The Tutor LMS – eLearning and online course solution plugin for WordPress is vulnerable to unauthorized course enrollment in all versions up to, and including, 3.9.3. This is due to a missing capability check and purchasability validation in the `course_enrollment()` AJAX handler. This makes it possible for authenticated attackers, with subscriber level access and above, to enroll themselves in any course without going through the proper purchase flow.
--- cache/tutor_3.9.3/ecommerce/CouponController.php 2026-01-09 00:38:03.500150409 +0000+++ cache/tutor_3.9.4/ecommerce/CouponController.php 2026-01-09 00:38:53.571198948 +0000@@ -577,10 +577,7 @@ */ public function bulk_action_handler() { tutor_utils()->checking_nonce();-- if ( ! current_user_can( 'manage_options' ) ) {- tutor_utils()->error_message();- }+ tutor_utils()->check_current_user_capability(); // Get and sanitize input data. $request = Input::sanitize_array( $_POST ); //phpcs:ignore --sanitized already@@ -630,9 +627,7 @@ public function coupon_permanent_delete() { tutor_utils()->checking_nonce(); - if ( ! current_user_can( 'manage_options' ) ) {- tutor_utils()->error_message();- }+ tutor_utils()->check_current_user_capability(); // Get and sanitize input data. $id = Input::post( 'id', 0, Input::TYPE_INT );
```
Vulnerability Existed: no
Authorization Bypass Prevention - CWE-639 - ecommerce/CouponController.php [580-583, 630-633]
Old Code:
if ( ! current_user_can( 'manage_options' ) ) {
tutor_utils()->error_message();
}
Fixed Code:
tutor_utils()->check_current_user_capability();
Explanation:
The old code had a critical authorization vulnerability. When a user lacked the 'manage_options' capability, the code would call tutor_utils()->error_message() but would NOT exit/return, allowing execution to continue to the sensitive operations below (bulk actions and permanent deletion). This is a classic authorization bypass - the function would display an error message but then proceed to process coupon operations anyway.
The fix replaces this with tutor_utils()->check_current_user_capability(), which presumably performs proper capability checking AND terminates execution if the user lacks authorization (typical implementation pattern for such utility functions). This prevents the authorization bypass by ensuring that unprivileged users cannot execute bulk coupon actions or permanent deletions.
This fix addresses CWE-639 (Authorization Bypass Through User-Controlled Key) by properly enforcing authorization checks before allowing sensitive operations.
```
The Tutor LMS – eLearning and online course solution plugin for WordPress is vulnerable to unauthorized course enrollment in all versions up to, and including, 3.9.3. This is due to a missing capability check and purchasability validation in the `course_enrollment()` AJAX handler. This makes it possible for authenticated attackers, with subscriber level access and above, to enroll themselves in any course without going through the proper purchase flow.
--- cache/tutor_3.9.3/ecommerce/OrderController.php 2026-01-09 00:38:03.500150409 +0000+++ cache/tutor_3.9.4/ecommerce/OrderController.php 2026-01-09 00:38:53.571198948 +0000@@ -84,7 +84,7 @@ * * @since 3.0.0 */- add_action( 'wp_ajax_tutor_order_details', array( $this, 'get_order_by_id' ) );+ add_action( 'wp_ajax_tutor_order_details', array( $this, 'ajax_get_order_details' ) ); /** * Handle AJAX request for marking an order as paid by order ID.@@ -258,10 +258,9 @@ * * @return void */- public function get_order_by_id() {- if ( ! tutor_utils()->is_nonce_verified() ) {- $this->json_response( tutor_utils()->error_message( 'nonce' ), null, HttpHelper::STATUS_BAD_REQUEST );- }+ public function ajax_get_order_details() {+ tutor_utils()->check_nonce();+ tutor_utils()->check_current_user_capability(); $order_id = Input::post( 'order_id' );
Let me analyze the diff you provided:
**Vulnerability 1:**
Vulnerability Existed: yes
FALSE POSITIVE
Cross-Site Request Forgery (CSRF) - CWE-352 - ecommerce/OrderController.php lines 261-262
Old Code:
if ( ! tutor_utils()->is_nonce_verified() ) {
$this->json_response( tutor_utils()->error_message( 'nonce' ), null, HttpHelper::STATUS_BAD_REQUEST );
}
Fixed Code:
tutor_utils()->check_nonce();
tutor_utils()->check_current_user_capability();
Explanation:
The original code had a CSRF vulnerability. The old implementation used `is_nonce_verified()` which only checks if the nonce is valid but does not terminate execution on failure - it relies on the conditional to return early. However, the fixed code calls `check_nonce()` which should perform the nonce validation and halt execution if verification fails, preventing CSRF attacks. Additionally, the fix adds an explicit capability check via `check_current_user_capability()` which provides defense-in-depth by ensuring the user has appropriate permissions to access this AJAX endpoint. The rename from `get_order_by_id()` to `ajax_get_order_details()` also improves clarity that this is an AJAX handler.
**Vulnerability 2:**
Vulnerability Existed: yes
TRUE POSITIVE
Missing Authorization Check - CWE-862 - ecommerce/OrderController.php lines 261-262
Old Code:
if ( ! tutor_utils()->is_nonce_verified() ) {
$this->json_response( tutor_utils()->error_message( 'nonce' ), null, HttpHelper::STATUS_BAD_REQUEST );
}
Fixed Code:
tutor_utils()->check_nonce();
tutor_utils()->check_current_user_capability();
Explanation:
The original code only verified the nonce but did not check user capabilities/permissions. The fixed code explicitly adds `tutor_utils()->check_current_user_capability()` which performs authorization checks. This prevents unauthorized users from accessing order details they shouldn't have access to, addressing a missing authorization vulnerability (CWE-862).
The Tutor LMS – eLearning and online course solution plugin for WordPress is vulnerable to unauthorized course enrollment in all versions up to, and including, 3.9.3. This is due to a missing capability check and purchasability validation in the `course_enrollment()` AJAX handler. This makes it possible for authenticated attackers, with subscriber level access and above, to enroll themselves in any course without going through the proper purchase flow.
--- cache/tutor_3.9.3/models/CouponModel.php 2026-01-09 00:38:03.552153573 +0000+++ cache/tutor_3.9.4/models/CouponModel.php 2026-01-09 00:38:53.815213812 +0000@@ -810,7 +810,7 @@ } else { $coupon = $this->get_coupon( array(- 'coupon_code' => $coupon_code,+ 'coupon_code' => esc_sql( $coupon_code ), 'coupon_status' => self::STATUS_ACTIVE, ) );
Vulnerability Existed: yes TRUE POSITIVE SQL Injection - CWE-89 - models/CouponModel.php - Line 813 Old Code: ```php 'coupon_code' => $coupon_code, ``` Fixed Code: ```php 'coupon_code' => esc_sql( $coupon_code ), ``` Explanation: The `$coupon_code` variable is passed directly to the `get_coupon()` method without sanitization. This creates an SQL injection vulnerability if the coupon code parameter comes from user input (via GET, POST, or other external sources). An attacker could inject malicious SQL code through the coupon code parameter. The fix applies `esc_sql()`, a WordPress function that escapes SQL special characters, preventing SQL injection attacks by properly escaping the user-supplied input before it's used in a database query.
The Tutor LMS – eLearning and online course solution plugin for WordPress is vulnerable to unauthorized course enrollment in all versions up to, and including, 3.9.3. This is due to a missing capability check and purchasability validation in the `course_enrollment()` AJAX handler. This makes it possible for authenticated attackers, with subscriber level access and above, to enroll themselves in any course without going through the proper purchase flow.
--- cache/tutor_3.9.3/tutor.php 2026-01-09 00:38:03.584155521 +0000+++ cache/tutor_3.9.4/tutor.php 2026-01-09 00:38:53.923220392 +0000@@ -4,7 +4,7 @@ * Plugin URI: https://tutorlms.com * Description: Tutor is a complete solution for creating a Learning Management System in WordPress way. It can help you to create small to large scale online education site very conveniently. Power features like report, certificate, course preview, private file sharing make Tutor a robust plugin for any educational institutes. * Author: Themeum- * Version: 3.9.3+ * Version: 3.9.4 * Author URI: https://themeum.com * Requires PHP: 7.4 * Requires at least: 5.3@@ -26,7 +26,7 @@ * * @since 1.0.0 */-define( 'TUTOR_VERSION', '3.9.3' );+define( 'TUTOR_VERSION', '3.9.4' ); define( 'TUTOR_FILE', __FILE__ ); /**
I'll analyze the provided code diff for security vulnerabilities. **Analysis Results:** Vulnerability Existed: no No security vulnerabilities identified in this diff. **Explanation:** This diff only contains version number updates in the plugin header and constant definition: - Line 7: Plugin version header changed from '3.9.3' to '3.9.4' - Line 29: TUTOR_VERSION constant changed from '3.9.3' to '3.9.4' These are routine version bumps with no changes to executable code, input validation, data handling, authentication mechanisms, or any other security-relevant functionality. Version string updates alone do not introduce or fix security vulnerabilities.
The Tutor LMS – eLearning and online course solution plugin for WordPress is vulnerable to unauthorized course enrollment in all versions up to, and including, 3.9.3. This is due to a missing capability check and purchasability validation in the `course_enrollment()` AJAX handler. This makes it possible for authenticated attackers, with subscriber level access and above, to enroll themselves in any course without going through the proper purchase flow.
--- cache/tutor_3.9.3/vendor/composer/installed.php 2026-01-09 00:38:03.588155764 +0000+++ cache/tutor_3.9.4/vendor/composer/installed.php 2026-01-09 00:38:53.927220635 +0000@@ -3,7 +3,7 @@ 'name' => 'themeum/tutor', 'pretty_version' => 'dev-master', 'version' => 'dev-master',- 'reference' => 'a5dd7291aa5fa582e64a050945b7ec437a833cc5',+ 'reference' => '043bcc9b76cd1e56219d167b8be313b5aa933109', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(),@@ -13,7 +13,7 @@ 'themeum/tutor' => array( 'pretty_version' => 'dev-master', 'version' => 'dev-master',- 'reference' => 'a5dd7291aa5fa582e64a050945b7ec437a833cc5',+ 'reference' => '043bcc9b76cd1e56219d167b8be313b5aa933109', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(),
Vulnerability Existed: no No Vulnerabilities Detected - [N/A] - [vendor/composer/installed.php] [Lines 6, 16] Old Code: ``` 'reference' => 'a5dd7291aa5fa582e64a050945b7ec437a833cc5', ``` Fixed Code: ``` 'reference' => '043bcc9b76cd1e56219d167b8be313b5aa933109', ``` Explanation: This diff shows a version reference update in the Composer lock file, changing the git commit reference from `a5dd7291aa5fa582e64a050945b7ec437a833cc5` to `043bcc9b76cd1e56219d167b8be313b5aa933109`. This is a routine dependency version update (from Tutor 3.9.3 to 3.9.4) and does not introduce or fix any security vulnerabilities. The change is merely metadata that tracks which commit of the themeum/tutor package is installed. No code logic, access controls, input validation, or security-related functionality is modified in this diff.
The Tutor LMS – eLearning and online course solution plugin for WordPress is vulnerable to unauthorized course enrollment in all versions up to, and including, 3.9.3. This is due to a missing capability check and purchasability validation in the `course_enrollment()` AJAX handler. This makes it possible for authenticated attackers, with subscriber level access and above, to enroll themselves in any course without going through the proper purchase flow.
--- cache/tutor_3.9.3/views/pages/ecommerce/order-list.php 2026-01-09 00:38:03.596156251 +0000+++ cache/tutor_3.9.4/views/pages/ecommerce/order-list.php 2026-01-09 00:38:53.999225022 +0000@@ -27,7 +27,7 @@ $limit = (int) tutor_utils()->get_option( 'pagination_per_page', 10 ); $offset = ( $limit * $paged_filter ) - $limit; -$order_controller = new OrderController();+$order_controller = new OrderController( false ); $get_orders = $order_controller->get_orders( $limit, $offset ); $orders = $get_orders['results'];
Vulnerability Existed: yes FALSE POSITIVE Improper Input Validation - CWE-20 / Insufficient Access Control - CWE-284 - views/pages/ecommerce/order-list.php - Line 30 Old Code: ```php $order_controller = new OrderController(); ``` Fixed Code: ```php $order_controller = new OrderController( false ); ``` Explanation: The vulnerability existed in the original code. The `OrderController` is being instantiated without any access control parameter. The fix passes `false` as an argument to the constructor, which likely disables or restricts some privileged functionality (possibly admin-only operations or unrestricted data access). Without examining the `OrderController` class definition, the most probable vulnerability is that the controller was performing operations without proper authorization checks when instantiated without parameters. The `false` parameter likely forces the controller into a restricted mode that validates user permissions before allowing sensitive operations. This is consistent with CWE-284 (Improper Access Control) where sensitive functionality was accessible without proper authorization enforcement. The fix implements access control by passing a parameter that triggers permission validation within the controller.
The Tutor LMS – eLearning and online course solution plugin for WordPress is vulnerable to unauthorized course enrollment in all versions up to, and including, 3.9.3. This is due to a missing capability check and purchasability validation in the `course_enrollment()` AJAX handler. This makes it possible for authenticated attackers, with subscriber level access and above, to enroll themselves in any course without going through the proper purchase flow.
--- cache/tutor_3.9.3/views/pages/view_attempt.php 2026-01-09 00:38:03.596156251 +0000+++ cache/tutor_3.9.4/views/pages/view_attempt.php 2026-01-09 00:38:54.003225265 +0000@@ -21,12 +21,18 @@ $attempt_data = $attempt; $user_id = tutor_utils()->avalue_dot( 'user_id', $attempt_data ); $quiz_id = $attempt && isset( $attempt->quiz_id ) ? $attempt->quiz_id : 0;+$course_id = tutor_utils()->avalue_dot( 'course_id', $attempt_data ); if ( ! $attempt ) {- tutor_utils()->tutor_empty_state( __( 'Attemp not found', 'tutor' ) );+ tutor_utils()->tutor_empty_state( __( 'Attempt not found', 'tutor' ) ); return; } if ( 0 === $quiz_id ) {- tutor_utils()->tutor_empty_state( __( 'Attemp not found', 'tutor' ) );+ tutor_utils()->tutor_empty_state( __( 'Attempt not found', 'tutor' ) );+ return;+}++if ( ! tutor_utils()->is_instructor_of_this_course( get_current_user_id(), $course_id ) ) {+ tutor_utils()->tutor_empty_state(); return; }
```
Vulnerability Existed: yes
TRUE POSITIVE
Missing Authorization Check - CWE-639 - views/pages/view_attempt.php [Lines 21-32]
Old Code:
$attempt_data = $attempt;
$user_id = tutor_utils()->avalue_dot( 'user_id', $attempt_data );
$quiz_id = $attempt && isset( $attempt->quiz_id ) ? $attempt->quiz_id : 0;
if ( ! $attempt ) {
tutor_utils()->tutor_empty_state( __( 'Attemp not found', 'tutor' ) );
return;
}
if ( 0 === $quiz_id ) {
tutor_utils()->tutor_empty_state( __( 'Attemp not found', 'tutor' ) );
return;
}
Fixed Code:
$attempt_data = $attempt;
$user_id = tutor_utils()->avalue_dot( 'user_id', $attempt_data );
$quiz_id = $attempt && isset( $attempt->quiz_id ) ? $attempt->quiz_id : 0;
$course_id = tutor_utils()->avalue_dot( 'course_id', $attempt_data );
if ( ! $attempt ) {
tutor_utils()->tutor_empty_state( __( 'Attempt not found', 'tutor' ) );
return;
}
if ( 0 === $quiz_id ) {
tutor_utils()->tutor_empty_state( __( 'Attempt not found', 'tutor' ) );
return;
}
if ( ! tutor_utils()->is_instructor_of_this_course( get_current_user_id(), $course_id ) ) {
tutor_utils()->tutor_empty_state();
return;
}
Explanation:
The original code was missing an authorization check. The page loaded attempt data and allowed processing without verifying that the current user is an instructor of the course associated with the attempt. This could allow unauthorized users (such as other instructors or students) to access attempt details they shouldn't have access to. The fix adds an explicit authorization check using `is_instructor_of_this_course()` to verify the current user is an instructor of the course before proceeding, preventing unauthorized access to quiz attempt data.
```
The Tutor LMS – eLearning and online course solution plugin for WordPress is vulnerable to unauthorized course enrollment in all versions up to, and including, 3.9.3. This is due to a missing capability check and purchasability validation in the `course_enrollment()` AJAX handler. This makes it possible for authenticated attackers, with subscriber level access and above, to enroll themselves in any course without going through the proper purchase flow.