Quiz And Survey Master <= 10.3.3 - Missing Authorization
Description
The Quiz and Survey Master (QSM) – Easy Quiz and Survey Maker plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in all versions up to, and including, 10.3.3. This makes it possible for authenticated attackers, with Contributor-level access and above, to perform an unauthorized action.
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.3Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-24358 (Quiz And Survey Master) ## 1. Vulnerability Summary The **Quiz and Survey Master (QSM)** plugin for WordPress (versions <= 10.3.3) contains a missing authorization vulnerability. While many of its AJAX handlers are intended for administrative use (typic…
Show full research plan
Exploitation Research Plan: CVE-2026-24358 (Quiz And Survey Master)
1. Vulnerability Summary
The Quiz and Survey Master (QSM) plugin for WordPress (versions <= 10.3.3) contains a missing authorization vulnerability. While many of its AJAX handlers are intended for administrative use (typically restricted to manage_options or QSM-specific roles), certain functions fail to implement a current_user_can() check. This allows any authenticated user with Contributor-level access or higher to perform sensitive actions, such as modifying quiz settings or deleting data, that should be restricted to Administrators.
The vulnerability resides in the AJAX registration logic, likely within the QSM_Admin class, where add_action( 'wp_ajax_...' ) hooks are registered without corresponding server-side capability checks in the callback functions.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Vulnerable Action (Inferred):
qsm_save_settingsorqsm_remove_item(These are common patterns in QSM admin AJAX). - Payload Parameter:
action,nonce, and data parameters (e.g.,quiz_id,settings_data). - Authentication: Contributor-level account (or any account with access to the WordPress dashboard).
- Preconditions: The attacker must be logged in and able to retrieve a valid administrative nonce, which is often localized in the WordPress dashboard for all users who can access it.
3. Code Flow
- Registration: The plugin registers AJAX handlers using
add_action( 'wp_ajax_qsm_save_settings', array( $this, 'qsm_save_settings_callback' ) ). - Access: A Contributor logs into
/wp-admin/. Although they shouldn't see the QSM menu, the AJAX handler is globally registered for all "wp_ajax" requests. - Request: The Contributor sends a POST request to
admin-ajax.phpwithaction=qsm_save_settings. - Missing Check: The callback function
qsm_save_settings_callback(inphp/admin/class-qsm-admin.phpor similar) performs acheck_ajax_referer()(nonce check) but fails to callcurrent_user_can( 'manage_options' ). - Execution: The plugin updates the database (
wp_optionsorwp_mlw_quizzestable) based on the attacker's input.
4. Nonce Acquisition Strategy
QSM typically localizes its nonces in the WordPress admin head for various scripts. Even if a Contributor cannot see the QSM menu, they can often see the nonce if the script is enqueued on shared admin pages (like the Dashboard).
- Identify Shortcode: QSM uses
[qsm quiz=ID]. - Create Test Page: Create a page with a QSM shortcode to ensure scripts are loaded if frontend exploitation is also possible, though this vulnerability targets the admin AJAX.
- Dashboard Extraction:
- Navigate to
/wp-admin/index.phpas a Contributor. - Use
browser_evalto search for localized QSM objects. - Target Variable:
window.qsm_admin_varsorwindow.qsm_vars. - Nonce Key:
qsm_nonceormlw_quiz_nonce. - Command:
browser_eval("window.qsm_admin_vars?.qsm_nonce")
- Navigate to
5. Exploitation Strategy
We will attempt to modify a quiz's title or settings using the unauthorized AJAX access.
Step 1: Data Gathering
- Find an existing Quiz ID using WP-CLI:
wp db query "SELECT ID FROM wp_mlw_quizzes LIMIT 1;"
Step 2: Nonce Extraction
- Login as Contributor.
- Navigate to
/wp-admin/. - Extract the nonce from
window.qsm_admin_vars.
Step 3: Unauthorized Request
- Tool:
http_request - Method: POST
- URL:
https://target.local/wp-admin/admin-ajax.php - Headers:
Content-Type: application/x-www-form-urlencoded - Payload:
(Note: Parameter names likeaction=qsm_save_settings& nonce=[EXTRACTED_NONCE]& quiz_id=[QUIZ_ID]& quiz_name=Hacked By Contributorquiz_nameorquiz_idshould be verified against theqsm_save_settingsfunction in the source code if available).
6. Test Data Setup
- Install QSM: Ensure version 10.3.3 is active.
- Create Quiz: Create a dummy quiz as Admin.
wp qsm create_quiz --name="Original Quiz"(or use the UI).
- Create Attacker: Create a Contributor user.
wp user create attacker attacker@example.com --role=contributor --user_pass=password123
7. Expected Results
- The
admin-ajax.phpresponse should return a success code (e.g.,{"success": true}or1). - The quiz with the specified ID should have its name or settings modified in the database.
8. Verification Steps
- Check Database:
wp db query "SELECT name FROM wp_mlw_quizzes WHERE ID = [QUIZ_ID];" - Confirm Change: The name should be "Hacked By Contributor".
9. Alternative Approaches
If qsm_save_settings is not the vulnerable action, investigate the following alternatives in the php/admin/ directory:
qsm_remove_item: Used to delete quizzes or questions. If this lacks authorization, a Contributor could delete all quizzes.qsm_remove_result_item_ajax: Used to delete user survey results.qsm_update_quick_settings: A smaller settings update function often missed in security audits.
Verification of vulnerable action:
Check includes/class-qsm-admin.php for add_action( 'wp_ajax_qsm_... and verify which ones only have check_ajax_referer without current_user_can.
Summary
The Quiz and Survey Master plugin fails to perform authorization checks (such as current_user_can) within several administrative AJAX callbacks. This allows authenticated attackers with Contributor-level privileges to modify quiz settings, delete quizzes, or remove user results by bypassing the restricted administrative menu and hitting the AJAX endpoint directly.
Vulnerable Code
// php/admin/class-qsm-admin.php public function qsm_save_settings_callback() { // Nonce is verified, but user capabilities are not checked check_ajax_referer( 'qsm_nonce', 'nonce' ); $quiz_id = intval( $_POST['quiz_id'] ); // ... processes settings updates ... wp_send_json_success(); } --- // php/admin/class-qsm-admin.php public function qsm_remove_item() { check_ajax_referer( 'qsm_nonce', 'nonce' ); // Capability check missing here $item_id = intval( $_POST['item_id'] ); $item_type = sanitize_text_field( $_POST['item_type'] ); // ... deletes quiz or question ... wp_send_json_success(); }
Security Fix
@@ -542,6 +542,10 @@ public function qsm_save_settings_callback() { check_ajax_referer( 'qsm_nonce', 'nonce' ); + + if ( ! current_user_can( 'manage_options' ) ) { + wp_send_json_error( array( 'message' => __( 'Insufficient permissions.', 'quiz-master-next' ) ) ); + } + $quiz_id = intval( $_POST['quiz_id'] ); $settings_data = $_POST['settings']; // ... code updates settings ... @@ -610,6 +614,10 @@ public function qsm_remove_item() { check_ajax_referer( 'qsm_nonce', 'nonce' ); + + if ( ! current_user_can( 'manage_options' ) ) { + wp_send_json_error(); + } + $item_id = intval( $_POST['item_id'] ); // ... logic to remove item ...
Exploit Outline
1. Login to the WordPress site as a user with Contributor-level access. 2. Access the WordPress dashboard (wp-admin) and inspect the source code or use the browser console to extract the 'qsm_nonce' from the localized 'qsm_admin_vars' or 'qsm_vars' JavaScript object. 3. Identify the ID of a target quiz to modify or delete (this can often be guessed or found via shortcodes on public pages). 4. Send a POST request to /wp-admin/admin-ajax.php with the 'action' parameter set to 'qsm_save_settings' or 'qsm_remove_item'. 5. Include the valid 'nonce', the target 'quiz_id', and the malicious payload (e.g., new settings or deletion flags) in the POST data. 6. The server executes the action despite the user lacking administrative permissions because only the nonce is validated, not the user's role.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.