REPORT / 01

Analysis Report · Folder Analysis cache/quiz-master-next_10.3.1 → cache/quiz-master-next_10.3.2 — CVE-2025-9318

Shared security patch analysis results

mode patchdiff ai claude_cli haiku
02 · Lifecycle actions cancel · resume · skip · regenerate
03 · Share this analysis copy link · embed report
03 · CVE Security Analysis & Writeups ai-generated · per cve

Comprehensive 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.

CVE-2025-9318 NVD
AI-Generated Analysis
05 · Findings filter · search · paginate
Use quotes for exact: "SQL injection" · Operators: hello AND bye, admin OR root, -error, NOT warning
Showing 0 to 0 of 0 results
php/rest-api.php AI: 2 vulnerabilities 1 false positive, 1 true positive CVE-2025-9318
--- cache/quiz-master-next_10.3.1/php/rest-api.php	2026-01-08 00:36:12.945991557 +0000+++ cache/quiz-master-next_10.3.2/php/rest-api.php	2026-01-08 00:38:10.081095233 +0000@@ -20,7 +20,9 @@ 		array( 			'methods'             => WP_REST_Server::READABLE, 			'callback'            => 'qsm_rest_get_questions',-			'permission_callback' => '__return_true',+			'permission_callback' => function () {+				return current_user_can( 'edit_qsm_quizzes' );+			}, 		) 	); 	register_rest_route(@@ -51,7 +53,9 @@ 		array( 			'methods'             => WP_REST_Server::READABLE, 			'callback'            => 'qsm_rest_get_question',-			'permission_callback' => '__return_true',+			'permission_callback' => function () {+				return current_user_can( 'edit_qsm_quizzes' );+			}, 		) 	); 	register_rest_route(@@ -60,7 +64,9 @@ 		array( 			'methods'             => WP_REST_Server::READABLE, 			'callback'            => 'qsm_rest_get_results',-			'permission_callback' => '__return_true',+			'permission_callback' => function () {+				return current_user_can( 'edit_qsm_quizzes' );+			}, 		) 	); 	register_rest_route(@@ -80,7 +86,9 @@ 		array( 			'methods'             => WP_REST_Server::READABLE, 			'callback'            => 'qsm_rest_get_emails',-			'permission_callback' => '__return_true',+			'permission_callback' => function () {+				return current_user_can( 'edit_qsm_quizzes' );+			}, 		) 	); 	register_rest_route(@@ -94,25 +102,29 @@ 			}, 		) 	);-		// Register rest api to get quiz list+		// Register rest api to get quiz list (admin-only) 		register_rest_route( 			'qsm', 			'/list_quiz', 			array( 				'methods'             => 'GET', 				'callback'            => 'qsm_get_basic_info_quiz',-				'permission_callback' => '__return_true',+				'permission_callback' => function () {+					return current_user_can( 'edit_qsm_quizzes' );+				}, 			) 		); -		// Register rest api to get result of quiz+		// Register rest api to get result of quiz (admin-only) 		register_rest_route( 			'qsm', 			'/list_results/(?P<id>\d+)', 			array( 				'methods'             => 'GET', 				'callback'            => 'qsm_get_result_of_quiz',-				'permission_callback' => '__return_true',+				'permission_callback' => function () {+					return current_user_can( 'edit_qsm_quizzes' );+				}, 			) 		); 		// Get questions for question bank@@ -127,27 +139,30 @@ 				}, 			) 		);-		// Get Categories of quiz+		// Get Categories of quiz (admin-only) 		register_rest_route( 			'quiz-survey-master/v1', 			'/quizzes/(?P<id>\d+)/categories', 			array( 				'methods'             => WP_REST_Server::READABLE, 				'callback'            => 'qsm_rest_get_categories',-				'permission_callback' => '__return_true',+				'permission_callback' => function () {+					return current_user_can( 'edit_qsm_quizzes' );+				}, 			) 		);-		// Get Categories of quiz+		// Get quizzes list (admin-only REST). 		register_rest_route( 			'quiz-survey-master/v2', 			'/quizzlist/', 			array( 				'methods'             => WP_REST_Server::READABLE, 				'callback'            => 'qsm_get_quizzes_list',-				'permission_callback' => '__return_true',+				'permission_callback' => function () {+					return current_user_can( 'edit_qsm_quizzes' );+				}, 			) 		);- }  /**@@ -214,7 +229,7 @@ 		$total_pages = ceil( $total_count / $limit ); 		$pageno = isset( $parameters['page'] ) ? intval( $parameters['page'] ) : 1; 		$offset = ( $pageno - 1 ) * $limit;-		$questions = [];+		$questions = array(); 		if ( ! empty( $category ) ) { 			if ( $migrated && is_numeric( $category ) ) { 				$query_result = array();@@ -511,26 +526,27 @@ 			$question       = QSM_Questions::load_question( $request['id'] ); 			$categorysArray = QSM_Questions::get_question_categories( $question['question_id'] ); 			if ( ! empty( $question ) ) {-				$is_linking = $request['is_linking'];-				$comma_separated_ids = '';-				if ( 1 <= $is_linking ) {-					if ( isset( $question['linked_question'] ) && '' == $question['linked_question'] ) {-						$comma_separated_ids = $is_linking;-					} else {-						$linked_question = isset($question['linked_question']) ? $question['linked_question'] : '';-						$exploded_question_array = explode(',', $linked_question);-						if ( ! empty($linked_question) ) {-							$exploded_question_array = array_merge([ $is_linking ], $exploded_question_array);-						} else {-							$exploded_question_array = [ $is_linking ];-						}-						$comma_separated_ids = implode(',', array_unique($exploded_question_array));+				$is_linking = isset( $request['is_linking'] ) ? intval( $request['is_linking'] ) : 0;+				$linked_ids = array();++				if ( isset( $question['linked_question'] ) && '' !== $question['linked_question'] ) {+					$existing_ids = array_map( 'intval', array_filter( array_map( 'trim', explode( ',', $question['linked_question'] ) ) ) );+					if ( ! empty( $existing_ids ) ) {+						$linked_ids = $existing_ids; 					} 				} +				if ( 1 <= $is_linking ) {+					$linked_ids[] = $is_linking;+				}++				$linked_ids = array_values( array_unique( array_filter( $linked_ids ) ) );+ 				$quiz_name_by_question = array();-				if ( ! empty($comma_separated_ids) ) {-					$quiz_results = $wpdb->get_results( "SELECT `quiz_id`, `question_id` FROM `{$wpdb->prefix}mlw_questions` WHERE `question_id` IN (" .$comma_separated_ids. ")" );+				if ( ! empty( $linked_ids ) ) {+					$linked_ids  = array_map( 'intval', $linked_ids );+					$ids_list    = implode( ',', $linked_ids );+					$quiz_results = $wpdb->get_results( "SELECT `quiz_id`, `question_id` FROM `{$wpdb->prefix}mlw_questions` WHERE `question_id` IN (" . $ids_list . ")" ); 					foreach ( $quiz_results as $value ) { 						$quiz_name_in_loop        = $wpdb->get_row( $wpdb->prepare( "SELECT quiz_name FROM {$wpdb->prefix}mlw_quizzes WHERE quiz_id = %d", $value->quiz_id ), ARRAY_A ); 						$quiz_name_in_loop = isset( $quiz_name_in_loop['quiz_name'] ) ? $quiz_name_in_loop['quiz_name'] : '';@@ -554,7 +570,7 @@ 					'page'            => $question['page'], 					'question_title'  => isset( $question['settings']['question_title'] ) ? $question['settings']['question_title'] : '', 					'link_quizzes'    => $quiz_name_by_question,-					'merged_question' => $comma_separated_ids,+					'merged_question' => implode( ',', $linked_ids ), 				); 			} 			return $question;@@ -609,7 +625,7 @@ 						} 					} 				}-				$quiz_name_by_question = array_diff($quiz_name_by_question, [ $quiz_name ]); // remove current quiz id from the list+				$quiz_name_by_question = array_diff($quiz_name_by_question, array( $quiz_name )); // remove current quiz id from the list 				$question_data    = array( 					'id'                      => $question['question_id'], 					'quizID'                  => $question['quiz_id'],
AI Analysis
I'll analyze this code diff for security vulnerabilities.

Vulnerability 1:

Vulnerability Existed: yes
TRUE POSITIVE
Broken Access Control (Insufficient Authorization) - CWE-639 - php/rest-api.php [lines 23-25, 56-58, 67-69, 89-91, 112-114, 124-126, 149-151, 162-164]
Old Code:
```php
'permission_callback' => '__return_true',
```
Fixed Code:
```php
'permission_callback' => function () {
    return current_user_can( 'edit_qsm_quizzes' );
},
```
Explanation:
The original code used `'__return_true'` as the permission callback, which means ALL REST API endpoints were publicly accessible without any authentication or authorization checks. This is a critical authorization bypass vulnerability. The fix properly implements permission checks using `current_user_can( 'edit_qsm_quizzes' )`, ensuring only users with the appropriate capability can access these endpoints. This applies to 8 separate REST route registrations across the file.

---

Vulnerability 2:

Vulnerability Existed: yes
FALSE POSITIVE
SQL Injection - CWE-89 - php/rest-api.php [lines 548-549]
Old Code:
```php
$comma_separated_ids = '';
if ( 1 <= $is_linking ) {
    if ( isset( $question['linked_question'] ) && '' == $question['linked_question'] ) {
        $comma_separated_ids = $is_linking;
    } else {
        $linked_question = isset($question['linked_question']) ? $question['linked_question'] : '';
        $exploded_question_array = explode(',', $linked_question);
        if ( ! empty($linked_question) ) {
            $exploded_question_array = array_merge([ $is_linking ], $exploded_question_array);
        } else {
            $exploded_question_array = [ $is_linking ];
        }
        $comma_separated_ids = implode(',', array_unique($exploded_question_array));
    }
}
...
$quiz_results = $wpdb->get_results( "SELECT `quiz_id`, `question_id` FROM `{$wpdb->prefix}mlw_questions` WHERE `question_id` IN (" .$comma_separated_ids. ")" );
```
Fixed Code:
```php
$is_linking = isset( $request['is_linking'] ) ? intval( $request['is_linking'] ) : 0;
$linked_ids = array();

if ( isset( $question['linked_question'] ) && '' !== $question['linked_question'] ) {
    $existing_ids = array_map( 'intval', array_filter( array_map( 'trim', explode( ',', $question['linked_question'] ) ) ) );
    if ( ! empty( $existing_ids ) ) {
        $linked_ids = $existing_ids;
    }
}

if ( 1 <= $is_linking ) {
    $linked_ids[] = $is_linking;
}

$linked_ids = array_values( array_unique( array_filter( $linked_ids ) ) );
...
if ( ! empty( $linked_ids ) ) {
    $linked_ids  = array_map( 'intval', $linked_ids );
    $ids_list    = implode( ',', $linked_ids );
    $quiz_results = $wpdb->get_results( "SELECT `quiz_id`, `question_id` FROM `{$wpdb->prefix}mlw_questions` WHERE `question_id` IN (" . $ids_list . ")" );
```
Explanation:
The original code failed to properly validate `$is_linking` from user input and directly concatenated it into the SQL query. While the data originated from the database, the unsanitized user input `$is_linking` could be injected into the SQL query string. The fix properly sanitizes this by: (1) explicitly casting `$is_linking` to `intval()`, (2) converting all array elements to integers using `array_map( 'intval', ... )`, and (3) only building the SQL query with these validated integer values. This prevents SQL injection attacks.
CVE Analysis Results:
CVE-2025-9318: Yes
View CVE Description
The Quiz and Survey Master (QSM) – Easy Quiz and Survey Maker plugin for WordPress is vulnerable to time-based SQL Injection via the ‘is_linking’ parameter in all versions up to, and including, 10.3.1 due to insufficient escaping on the user supplied parameter and lack of sufficient preparation on the existing SQL query.  This makes it possible for authenticated attackers, with Subscriber-level access and above, to append additional SQL queries into already existing queries that can be used to extract sensitive information from the database.
Showing 1 to 1 of 1 results