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