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:39:33.417627205 +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: PatchLeaks Tutor Plugin Course.php
## Vulnerability 1
**Vulnerability Existed:** yes
**Improper Authorization Check - CWE-639** - CWE-639 - classes/Course.php - Lines 2122-2124
**Old Code:**
```php
// No enrollment verification 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:**
The original code allowed any authenticated user to mark a course as complete without verifying enrollment status. This is an authorization bypass vulnerability where users could manipulate their progress for courses they never enrolled in. The fix adds an explicit enrollment check that terminates execution if the user is not enrolled, preventing unauthorized course completion claims.
---
## Vulnerability 2
**Vulnerability Existed:** yes
**Improper Access Control / Unauthorized Enrollment - CWE-639** - CWE-639 - classes/Course.php - Lines 3009-3020
**Old Code:**
```php
if ( $password_protected ) {
wp_send_json_error( __( 'This course is password protected', 'tutor' ) );
}
// Proceeds directly to enrollment without verifying payment for paid courses
$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:**
The original AJAX enrollment endpoint allowed users to enroll in paid courses without purchasing them. An attacker could directly call this AJAX function to bypass the payment system. The fix implements a critical authorization control by checking if a course is purchasable, and if so, verifying the user either already has access (from a prior purchase) or rejecting the enrollment with an error message. This prevents free enrollment in paid courses.
---
## Summary
Both vulnerabilities represent **Authorization Bypass** issues (CWE-639) in course access control. The patches add necessary permission checks before allowing users to complete courses or enroll in paid courses, addressing what appear to be critical access control flaws in the Tutor LMS plugin.
The Tutor LMS – eLearning and online course solution plugin for WordPress is vulnerable to unauthorized modification and deletion of data due to a missing capability check on the 'bulk_action_handler' and 'coupon_permanent_delete' functions in all versions up to, and including, 3.9.3. This makes it possible for authenticated attackers, with subscriber level access and above, to delete, activate, deactivate, or trash arbitrary coupons.
--- 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:39:33.437628424 +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
FALSE 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 submission. Any authenticated user could submit feedback for any quiz attempt without verifying they are an instructor of the course. The fix adds an explicit access control check that verifies the current user is an instructor of the course before allowing feedback submission, preventing unauthorized users from modifying quiz attempt feedback.
```
The Tutor LMS – eLearning and online course solution plugin for WordPress is vulnerable to unauthorized modification and deletion of data due to a missing capability check on the 'bulk_action_handler' and 'coupon_permanent_delete' functions in all versions up to, and including, 3.9.3. This makes it possible for authenticated attackers, with subscriber level access and above, to delete, activate, deactivate, or trash arbitrary coupons.
--- 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:39:33.457629644 +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 );
I'll analyze this code diff for security fixes.
Vulnerability Existed: yes
TRUE POSITIVE
[Improper Authorization Check] - [CWE-862] - [ecommerce/CouponController.php] [Lines 580-583, 630-633]
Old Code:
```php
if ( ! current_user_can( 'manage_options' ) ) {
tutor_utils()->error_message();
}
```
Fixed Code:
```php
tutor_utils()->check_current_user_capability();
```
Explanation:
The old code had a critical authorization bypass vulnerability. When the authorization check failed (user did not have 'manage_options' capability), the code only called `error_message()` but then **continued execution** of the subsequent function logic. This is a classic authorization bypass - the function would proceed to process the bulk action or permanent delete operation even though the user was not authorized.
The fix replaces this with a call to `tutor_utils()->check_current_user_capability()`, which appears to be a centralized authorization check that likely terminates execution with an error response when the capability check fails, preventing unauthorized operations from proceeding.
This vulnerability affected two methods:
1. `bulk_action_handler()` - could allow unauthorized bulk coupon operations
2. `coupon_permanent_delete()` - could allow unauthorized coupon deletion
The impact is **high severity** as it allows privilege escalation and unauthorized data modification/deletion operations.
The Tutor LMS – eLearning and online course solution plugin for WordPress is vulnerable to unauthorized modification and deletion of data due to a missing capability check on the 'bulk_action_handler' and 'coupon_permanent_delete' functions in all versions up to, and including, 3.9.3. This makes it possible for authenticated attackers, with subscriber level access and above, to delete, activate, deactivate, or trash arbitrary coupons.
--- 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:39:33.461629887 +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 provided diff directly based on the information given.
## Security Analysis
**Vulnerability 1: Improper CSRF Protection Implementation**
Vulnerability Existed: yes
TRUE POSITIVE
Cross-Site Request Forgery (CSRF) - CWE-352 - ecommerce/OrderController.php Lines 261-264
Old Code:
```php
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 );
}
}
```
Fixed Code:
```php
public function ajax_get_order_details() {
tutor_utils()->check_nonce();
tutor_utils()->check_current_user_capability();
}
```
Explanation:
The original code had a CSRF vulnerability. The `is_nonce_verified()` check was conditional - if the nonce verification failed, it returned an error response but the function continued executing. The nonce check lacked proper flow control. The fix replaces this with `check_nonce()`, which presumably throws an exception or terminates execution on failure, preventing unauthorized AJAX requests. Additionally, the new code adds `check_current_user_capability()` to verify user permissions, which is a defense-in-depth improvement.
---
**Vulnerability 2: Authorization Check Missing**
Vulnerability Existed: yes
TRUE POSITIVE
Missing Authorization Check - CWE-862 - ecommerce/OrderController.php Lines 261-264
Old Code:
```php
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 );
}
}
```
Fixed Code:
```php
public function ajax_get_order_details() {
tutor_utils()->check_nonce();
tutor_utils()->check_current_user_capability();
}
```
Explanation:
The original code only verified the nonce (CSRF protection) but did not check user capabilities/permissions. This is an authorization vulnerability - any authenticated user could potentially access order details they shouldn't have access to. The fix adds `check_current_user_capability()` to ensure the user has the appropriate permissions to view the requested order details, preventing privilege escalation.
The Tutor LMS – eLearning and online course solution plugin for WordPress is vulnerable to unauthorized modification and deletion of data due to a missing capability check on the 'bulk_action_handler' and 'coupon_permanent_delete' functions in all versions up to, and including, 3.9.3. This makes it possible for authenticated attackers, with subscriber level access and above, to delete, activate, deactivate, or trash arbitrary coupons.
--- 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:39:33.789649885 +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 was being passed directly to a database query without proper sanitization. The fix applies `esc_sql()`, a WordPress function that escapes SQL special characters to prevent SQL injection attacks. This prevents an attacker from injecting malicious SQL code through the coupon code parameter. The vulnerability existed because user-controlled input was being used in a database query without escaping, which is a classic SQL injection vector.
The Tutor LMS – eLearning and online course solution plugin for WordPress is vulnerable to unauthorized modification and deletion of data due to a missing capability check on the 'bulk_action_handler' and 'coupon_permanent_delete' functions in all versions up to, and including, 3.9.3. This makes it possible for authenticated attackers, with subscriber level access and above, to delete, activate, deactivate, or trash arbitrary coupons.
--- 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:39:34.057666224 +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__ ); /**
Vulnerability Existed: no No vulnerabilities detected in this diff. Explanation: This diff only updates the version number of the Tutor plugin from 3.9.3 to 3.9.4 in two locations (the plugin header and the TUTOR_VERSION constant definition). Version number changes are purely informational metadata and do not introduce, fix, or relate to any security vulnerabilities. There are no code logic changes, no input handling modifications, no permission checks, or any other security-relevant code alterations in this diff.
The Tutor LMS – eLearning and online course solution plugin for WordPress is vulnerable to unauthorized modification and deletion of data due to a missing capability check on the 'bulk_action_handler' and 'coupon_permanent_delete' functions in all versions up to, and including, 3.9.3. This makes it possible for authenticated attackers, with subscriber level access and above, to delete, activate, deactivate, or trash arbitrary coupons.
--- 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:39:34.061666468 +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(),
I'll analyze this code diff for security vulnerabilities. **Analysis Result:** Vulnerability Existed: no **Explanation:** This diff shows a change in the `vendor/composer/installed.php` file, which is a Composer package metadata file. The changes are: - Git commit reference updated from `a5dd7291aa5fa582e64a050945b7ec437a833cc5` to `043bcc9b76cd1e56219d167b8be313b5aa933109` (appearing in two locations) This is a routine package version/reference update. The file contains only metadata about installed dependencies and package information. There are no code changes, no new code execution paths, and no modifications to functional logic that could introduce or fix security vulnerabilities. This appears to be an automatic Composer lock file update when dependencies are refreshed or updated. Without access to the actual commit contents that this reference points to, I cannot assess whether the underlying package code itself contains security fixes, but the diff itself—being purely metadata updates—contains no vulnerability-related changes.
The Tutor LMS – eLearning and online course solution plugin for WordPress is vulnerable to unauthorized modification and deletion of data due to a missing capability check on the 'bulk_action_handler' and 'coupon_permanent_delete' functions in all versions up to, and including, 3.9.3. This makes it possible for authenticated attackers, with subscriber level access and above, to delete, activate, deactivate, or trash arbitrary coupons.
--- 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:39:34.133670858 +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'];
Based on my analysis, here is the security assessment: --- **Vulnerability Existed: not sure** **Improper Initialization of Object - CWE-665 - 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 change passes `false` to the `OrderController` constructor, which prevents automatic registration of 7 AJAX action hooks for handling order operations. This is a security-relevant change because it disables automatic hook registration, but the vulnerability classification is uncertain due to insufficient context: 1. **Potential Security Risk (if this was vulnerable):** Without the `false` parameter, the OrderController registered AJAX hooks globally whenever instantiated, even if those hooks weren't needed in this admin list view context. This could potentially expose additional AJAX endpoints unnecessarily. 2. **Defense in Depth Principle:** The fix appears to follow a principle of least privilege by only registering AJAX hooks when actually needed, rather than automatically registering them wherever the controller is instantiated. 3. **Uncertainty Factors:** - It's unclear if the original code (without the parameter) actually created a security vulnerability or just unnecessary functionality bloat - The vulnerability depends on whether WordPress properly validates AJAX requests and whether permission checks are properly enforced on those hooks - Without analyzing all 7 AJAX handlers to verify they have proper nonce and capability checks, it's difficult to confirm if exposing these hooks without need constitutes an actual vulnerability The change appears to be a security hardening measure following the principle of least privilege, but whether the original code was definitively vulnerable cannot be determined without additional context about WordPress's AJAX security model and the specific permission checks in those handlers.
The Tutor LMS – eLearning and online course solution plugin for WordPress is vulnerable to unauthorized modification and deletion of data due to a missing capability check on the 'bulk_action_handler' and 'coupon_permanent_delete' functions in all versions up to, and including, 3.9.3. This makes it possible for authenticated attackers, with subscriber level access and above, to delete, activate, deactivate, or trash arbitrary coupons.
--- 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:39:34.141671345 +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; }
Analyzing the provided code diff for security vulnerabilities:
---
**Vulnerability 1: Missing Authorization Check**
Vulnerability Existed: yes
FALSE POSITIVE
Improper Access Control - CWE-639 - views/pages/view_attempt.php Lines 34-36
Old Code:
```php
if ( 0 === $quiz_id ) {
tutor_utils()->tutor_empty_state( __( 'Attemp not found', 'tutor' ) );
return;
}
```
Fixed Code:
```php
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 lacked authorization verification before allowing access to quiz attempt data. Any authenticated user could view any attempt by accessing this page directly, regardless of whether they were the instructor of the course. The fix adds a check using `is_instructor_of_this_course()` to ensure only instructors of the course can view attempt details, preventing unauthorized information disclosure.
---
**Additional Changes (Non-Security):**
- Line 24: Added retrieval of `$course_id` from attempt data (required for the authorization check)
- Lines 26 & 31: Fixed typo in error message from "Attemp" to "Attempt" (not a security issue)
The Tutor LMS – eLearning and online course solution plugin for WordPress is vulnerable to unauthorized modification and deletion of data due to a missing capability check on the 'bulk_action_handler' and 'coupon_permanent_delete' functions in all versions up to, and including, 3.9.3. This makes it possible for authenticated attackers, with subscriber level access and above, to delete, activate, deactivate, or trash arbitrary coupons.