Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/quiz-master-next/blocks/block.php
+++ b/quiz-master-next/blocks/block.php
@@ -329,8 +329,14 @@
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'qsm_quiz_structure_data' ),
- 'permission_callback' => function () {
- return current_user_can( 'edit_qsm_quizzes' );
+ 'permission_callback' => function ( WP_REST_Request $request ) {
+ if ( ! current_user_can( 'edit_qsm_quizzes' ) ) {
+ return false;
+ }
+ $quiz_id = isset( $request['quizID'] ) ? intval( $request['quizID'] ) : 0;
+ return function_exists( 'qsm_current_user_can_edit_quiz' )
+ ? qsm_current_user_can_edit_quiz( $quiz_id )
+ : false;
},
)
);
@@ -438,6 +444,13 @@
return $result;
}
+ // Defense-in-depth: refuse to leak quiz structure or a usable rest_nonce
+ // for a quiz the current user is not authorized to edit.
+ if ( function_exists( 'qsm_current_user_can_edit_quiz' ) && ! qsm_current_user_can_edit_quiz( $quiz_id ) ) {
+ $result['msg'] = __( 'Unauthorized!', 'quiz-master-next' );
+ return $result;
+ }
+
global $wpdb;
@@ -604,6 +617,16 @@
);
}
+ // Enforce per-quiz ownership: the route's permission_callback only checks
+ // the broad edit_qsm_quizzes cap, so a contributor could otherwise rename
+ // or publish a quiz they do not own by passing its id in the request body.
+ if ( ! function_exists( 'qsm_current_user_can_edit_quiz' ) || ! qsm_current_user_can_edit_quiz( $quiz_id ) ) {
+ return array(
+ 'status' => 'error',
+ 'msg' => __( 'Unauthorized!', 'quiz-master-next' ),
+ );
+ }
+
global $mlwQuizMasterNext;
//Save Questions
if ( ! empty( $_POST['quizData']['questions'] ) ) {
--- a/quiz-master-next/mlw_quizmaster2.php
+++ b/quiz-master-next/mlw_quizmaster2.php
@@ -2,7 +2,7 @@
/**
* Plugin Name: Quiz And Survey Master
* Description: Easily and quickly add quizzes and surveys to your website.
- * Version: 11.1.4
+ * Version: 11.1.5
* Author: ExpressTech
* Author URI: https://quizandsurveymaster.com/
* Plugin URI: https://expresstech.io/
@@ -43,7 +43,7 @@
* @var string
* @since 4.0.0
*/
- public $version = '11.1.4';
+ public $version = '11.1.5';
/**
* QSM Alert Manager Object
--- a/quiz-master-next/php/admin/functions.php
+++ b/quiz-master-next/php/admin/functions.php
@@ -812,6 +812,9 @@
'%ANSWER_X%' => __( 'X = Question ID. It will show result of particular question.', 'quiz-master-next' ),
'%TIME_FINISHED%' => __( 'Display time after quiz submission.', 'quiz-master-next' ),
'%QUESTIONS_ANSWERS_EMAIL%' => __( 'Shows the question, the answer provided by user, and the correct answer.', 'quiz-master-next' ),
+ '%QSM_START_QUIZ_DATE%' => __( 'The scheduled start date/time of the quiz.', 'quiz-master-next' ),
+ '%QSM_END_QUIZ_DATE%' => __( 'The scheduled end date/time of the quiz.', 'quiz-master-next' ),
+
),
);
$variable_list = apply_filters( 'qsm_text_variable_list', $variable_list );
@@ -1556,6 +1559,11 @@
function qsm_insert_quiz_template_callback() {
global $wpdb;
+ if ( ! current_user_can( 'manage_options' ) ) {
+ wp_send_json_error( array( 'message' => __( 'You do not have permission to manage quiz templates.', 'quiz-master-next' ) ) );
+ wp_die();
+ }
+
// validate nonce
if ( isset( $_POST['nonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'qsm_add_template' ) && is_user_logged_in() ) {
@@ -1578,6 +1586,9 @@
},
$filtered_content
);
+ // Strip executable HTML (e.g. <script>, on* handlers) before persisting.
+ // Runs after the custom-tag passes above so %VARIABLE% / shortcode text are preserved.
+ $filtered_content = wp_kses_post( $filtered_content );
$table_name = $wpdb->prefix . 'mlw_quiz_output_templates';
@@ -1640,6 +1651,12 @@
*/
function qsm_remove_my_templates_handler() {
global $wpdb;
+
+ if ( ! current_user_can( 'manage_options' ) ) {
+ wp_send_json_error( array( 'message' => __( 'You do not have permission to manage quiz templates.', 'quiz-master-next' ) ) );
+ wp_die();
+ }
+
if ( ! isset( $_POST['nonce'] ) ||
! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'qsm_remove_template' )
) {
@@ -2107,4 +2124,4 @@
<?php
}
-add_action( 'admin_notices', 'qsm_show_legacy_fallback_notice' );
No newline at end of file
+add_action( 'admin_notices', 'qsm_show_legacy_fallback_notice' );
--- a/quiz-master-next/php/admin/options-page-email-tab.php
+++ b/quiz-master-next/php/admin/options-page-email-tab.php
@@ -301,7 +301,7 @@
</div>
<div class="qsm-email-page-then-box-styles-wrap">
<div class="qsm-email-page-common-section qsm-email-page-then-box-styles" >
- <label><?php esc_html_e( 'Who to send the email to? Put %USER_EMAIL% to send to user', 'quiz-master-next' ); ?></label>
+ <label><?php esc_html_e( 'Send to: Use %USER_EMAIL% for the user or %QSM_ADMIN_EMAIL% for the admin. Separate multiple emails with commas.', 'quiz-master-next' ); ?></label>
<?php do_action( 'qsm_after_send_email_label' ); ?>
<input type="email" class="qsm-to-email" value="{{ data.to }}">
<label class="qsm-email-reply-to">
--- a/quiz-master-next/php/classes/class-qsm-install.php
+++ b/quiz-master-next/php/classes/class-qsm-install.php
@@ -984,6 +984,8 @@
'%QUIZ_LINK%',
'%CURRENT_DATE%',
'%TOTAL_QUESTIONS%',
+ '%QSM_START_QUIZ_DATE%',
+ '%QSM_END_QUIZ_DATE%',
),
);
$mlwQuizMasterNext->pluginHelper->register_quiz_setting( $field_array, 'quiz_text' );
@@ -1059,6 +1061,8 @@
'%QUIZ_LINK%',
'%CURRENT_DATE%',
'%TOTAL_QUESTIONS%',
+ '%QSM_START_QUIZ_DATE%',
+ '%QSM_END_QUIZ_DATE%',
),
);
$mlwQuizMasterNext->pluginHelper->register_quiz_setting( $field_array, 'quiz_text' );
--- a/quiz-master-next/php/rest-api.php
+++ b/quiz-master-next/php/rest-api.php
@@ -464,6 +464,12 @@
// Makes sure user is logged in.
if ( is_user_logged_in() ) {
$current_user = wp_get_current_user();
+ if ( ! qsm_current_user_can_edit_quiz( $request['id'] ) ) {
+ return array(
+ 'status' => 'error',
+ 'msg' => __( 'Unauthorized!', 'quiz-master-next' ),
+ );
+ }
$stop = qsm_verify_rest_user_nonce( $request['id'], $current_user->ID, $request['rest_nonce'] );
if ( ! $stop ) {
if ( ! isset( $request['emails'] ) || ! is_array( $request['emails'] ) ) {
--- a/quiz-master-next/php/template-variables.php
+++ b/quiz-master-next/php/template-variables.php
@@ -59,6 +59,9 @@
add_filter( 'mlw_qmn_template_variable_quiz_page', 'mlw_qmn_variable_social_share', 10, 2 );
add_filter( 'mlw_qmn_template_variable_quiz_page', 'mlw_qmn_variable_total_questions', 10, 2 );
add_filter( 'mlw_qmn_template_variable_results_page', 'qsm_variable_minimum_points', 10, 2 );
+add_filter( 'mlw_qmn_template_variable_results_page', 'qsm_variable_start_end_quiz_time', 10, 2 );
+add_filter( 'mlw_qmn_template_variable_quiz_page', 'qsm_variable_start_end_quiz_time', 10, 2 );
+add_filter( 'mlw_qmn_template_variable_results_page', 'qsm_variable_admin_email', 10, 2 );
/**
* Changed the display structure to new structure.
@@ -1814,3 +1817,72 @@
return wp_kses_post( $question_display );
}
+
+/**
+ * Replaces %QSM_START_QUIZ_DATE% and %QSM_END_QUIZ_DATE% with the quiz's
+ * scheduled start/end date-time, formatted using the site's date/time settings.
+ */
+function qsm_variable_start_end_quiz_time( $content, $mlw_quiz_array ) {
+ $has_start = strpos( $content, '%QSM_START_QUIZ_DATE%' ) !== false;
+ $has_end = strpos( $content, '%QSM_END_QUIZ_DATE%' ) !== false;
+
+ if ( ! $has_start && ! $has_end ) {
+ return $content;
+ }
+
+ global $mlwQuizMasterNext;
+ $date_format = get_option( 'date_format' ) . ' ' . get_option( 'time_format' );
+
+ if ( $has_start ) {
+ $start_time_raw = '';
+ if ( isset( $mlw_quiz_array['scheduled_time_start'] ) && '' !== $mlw_quiz_array['scheduled_time_start'] ) {
+ $start_time_raw = $mlw_quiz_array['scheduled_time_start'];
+ } else {
+ $quiz_options = $mlwQuizMasterNext->pluginHelper->get_quiz_setting( 'quiz_options', array() );
+ if ( isset( $quiz_options['scheduled_time_start'] ) ) {
+ $start_time_raw = $quiz_options['scheduled_time_start'];
+ }
+ }
+ $start_formatted = '';
+ if ( '' !== $start_time_raw ) {
+ $timestamp = strtotime( $start_time_raw );
+ if ( $timestamp ) {
+ $start_formatted = date_i18n( $date_format, $timestamp );
+ }
+ }
+ $content = str_replace( '%QSM_START_QUIZ_DATE%', $start_formatted, $content );
+ }
+
+ if ( $has_end ) {
+ $end_time_raw = '';
+ if ( isset( $mlw_quiz_array['scheduled_time_end'] ) && '' !== $mlw_quiz_array['scheduled_time_end'] ) {
+ $end_time_raw = $mlw_quiz_array['scheduled_time_end'];
+ } else {
+ $quiz_options = $mlwQuizMasterNext->pluginHelper->get_quiz_setting( 'quiz_options', array() );
+ if ( isset( $quiz_options['scheduled_time_end'] ) ) {
+ $end_time_raw = $quiz_options['scheduled_time_end'];
+ }
+ }
+ $end_formatted = '';
+ if ( '' !== $end_time_raw ) {
+ $timestamp = strtotime( $end_time_raw );
+ if ( $timestamp ) {
+ $end_formatted = date_i18n( $date_format, $timestamp );
+ }
+ }
+ $content = str_replace( '%QSM_END_QUIZ_DATE%', $end_formatted, $content );
+ }
+
+ return $content;
+}
+
+/**
+ * Replaces %QSM_ADMIN_EMAIL% with the site's administrator email address.
+ */
+function qsm_variable_admin_email( $content, $mlw_quiz_array ) {
+ if ( strpos( $content, '%QSM_ADMIN_EMAIL%' ) === false ) {
+ return $content;
+ }
+ $admin_email = esc_html( get_option( 'admin_email', '' ) );
+ return str_replace( '%QSM_ADMIN_EMAIL%', $admin_email, $content );
+}