[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fbXxJAFaayMJy5i6uaQYSea7htU5WV0BK8lgyveiFt4I":3},{"id":4,"url_slug":5,"title":6,"description":7,"plugin_slug":8,"theme_slug":9,"affected_versions":10,"patched_in_version":11,"severity":12,"cvss_score":13,"cvss_vector":14,"vuln_type":15,"published_date":16,"updated_date":17,"references":18,"days_to_patch":20,"patch_diff_files":21,"patch_trac_url":9,"research_status":30,"research_verified":31,"research_rounds_completed":32,"research_plan":33,"research_summary":34,"research_vulnerable_code":9,"research_fix_diff":35,"research_exploit_outline":36,"research_model_used":37,"research_started_at":38,"research_completed_at":39,"research_error":9,"poc_status":9,"poc_video_id":9,"poc_summary":9,"poc_steps":9,"poc_tested_at":9,"poc_wp_version":9,"poc_php_version":9,"poc_playwright_script":9,"poc_exploit_code":9,"poc_has_trace":31,"poc_model_used":9,"poc_verification_depth":9,"poc_exploit_code_gated":31,"source_links":40},"CVE-2026-3225","learnpress-missing-authorization-to-authenticated-subscriber-arbitrary-quiz-answer-deletion","LearnPress \u003C= 4.3.2.8 - Missing Authorization to Authenticated (Subscriber+) Arbitrary Quiz Answer Deletion","The LearnPress – WordPress LMS Plugin plugin for WordPress is vulnerable to unauthorized deletion of quiz question answers due to a missing capability check in the delete_question_answer() function of the EditQuestionAjax class in all versions up to, and including, 4.3.2.8. The AbstractAjax::catch_lp_ajax() dispatcher verifies a wp_rest nonce but performs no current_user_can() check, and the QuestionAnswerModel::delete() method only validates minimum answer counts without checking user capabilities. This makes it possible for authenticated attackers, with Subscriber-level access and above, to delete answer options from any quiz question on the site.","learnpress",null,"\u003C=4.3.2.8","4.3.3","medium",4.3,"CVSS:3.1\u002FAV:N\u002FAC:L\u002FPR:L\u002FUI:N\u002FS:U\u002FC:N\u002FI:L\u002FA:N","Missing Authorization","2026-03-23 09:53:24","2026-03-23 22:25:40",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002Ffc4afb78-fca6-4cc2-8e64-4785d93055e6?source=api-prod",1,[22,23,24,25,26,27,28,29],"assets\u002Fcss\u002Fadmin\u002Fadmin-rtl.css","assets\u002Fcss\u002Fadmin\u002Fadmin-rtl.min.css","assets\u002Fcss\u002Fadmin\u002Fadmin.css","assets\u002Fcss\u002Fadmin\u002Fadmin.min.css","assets\u002Fcss\u002Fcourse-builder-rtl.css","assets\u002Fcss\u002Fcourse-builder-rtl.min.css","assets\u002Fcss\u002Fcourse-builder.css","assets\u002Fcss\u002Fcourse-builder.min.css","researched",false,3,"# Vulnerability Research Plan: CVE-2026-3225 - LearnPress Arbitrary Quiz Answer Deletion\n\n## 1. Vulnerability Summary\nLearnPress (\u003C= 4.3.2.8) contains a missing authorization vulnerability in its AJAX handling logic for editing quiz questions. The `AbstractAjax::catch_lp_ajax()` function acts as a dispatcher that verifies a `wp_rest` nonce but fails to perform any capability check (e.g., `current_user_can()`). This allows any authenticated user, such as a Subscriber, to invoke the `delete_question_answer()` method in the `EditQuestionAjax` class. This method triggers `QuestionAnswerModel::delete()`, which deletes specific answer options from a quiz question provided a minimum answer count (usually 2) is maintained.\n\n## 2. Attack Vector Analysis\n- **Endpoint**: `POST \u002F?lp-ajax=delete_question_answer` (The `AbstractAjax` dispatcher triggers based on the `lp-ajax` query parameter).\n- **Required Authentication**: Authenticated (Subscriber+ level).\n- **Payload Parameters**:\n    - `question_id`: The Post ID of the quiz question.\n    - `answer_id`: The unique ID of the answer option in the `learnpress_question_answers` database table.\n    - `_wpnonce`: A valid WordPress REST API nonce (`wp_rest`).\n- **Preconditions**:\n    - The attacker must be logged in to the WordPress site.\n    - The target question must have more than 2 answers (to satisfy the `QuestionAnswerModel` minimum count check).\n\n## 3. Code Flow\n1. **Entry Point**: A request is made to `POST \u002F?lp-ajax=delete_question_answer`.\n2. **Dispatcher**: `LearnPress\\REvent\\Ajax\\AbstractAjax::catch_lp_ajax()` (inferred namespace) is executed (hooked early, likely on `init` or `wp_loaded`).\n3. **Nonce Validation**: The dispatcher verifies the `_wpnonce` parameter against the `wp_rest` action using `wp_verify_nonce()`. \n4. **Missing Auth Check**: The dispatcher proceeds to call the method corresponding to the `lp-ajax` parameter value (`delete_question_answer`) without verifying if the current user has permissions like `edit_lp_questions`.\n5. **Vulnerable Method**: `LearnPress\\REvent\\Ajax\\EditQuestionAjax::delete_question_answer()` is called.\n6. **Sink**: `LearnPress\\Models\\QuestionAnswerModel::delete()` is called with the provided `question_id` and `answer_id`, performing the deletion in the `learnpress_question_answers` table.\n\n## 4. Nonce Acquisition Strategy\nThe `AbstractAjax` dispatcher requires a `wp_rest` nonce. This nonce is standard for the WordPress REST API and is frequently localized by LearnPress for its frontend and backend components.\n\n1. **Login**: Log in as a Subscriber user.\n2. **Navigate**: Navigate to the WordPress home page or any page where LearnPress scripts are loaded.\n3. **Extract**: Use `browser_eval` to extract the nonce from the `lpData` global JavaScript object, which LearnPress uses to store configuration data.\n    - **JS Command**: `window.lpData?.nonce` or `window.lpGlobalSettings?.nonce`.\n    - If not found in `lpData`, the standard WordPress REST nonce is often available at `window.wpApiSettings?.nonce`.\n\n## 5. Exploitation Strategy\n1. **Initial Setup**:\n    - As Admin, create a Quiz and a Question (e.g., Multiple Choice).\n    - Add 4 answer options to the question.\n    - Identify the `question_id` (Post ID) and the `answer_id` of one option (from the `wp_learnpress_question_answers` table).\n2. **Authentication**: Log in as a Subscriber.\n3. **Nonce Retrieval**: Execute `browser_eval` to obtain the `wp_rest` nonce.\n4. **Execution**: Send a `POST` request to the LearnPress AJAX dispatcher.\n\n**HTTP Request (via `http_request` tool):**\n```http\nPOST \u002F?lp-ajax=delete_question_answer HTTP\u002F1.1\nHost: localhost:8080\nContent-Type: application\u002Fx-www-form-urlencoded\nCookie: [Subscriber Cookies]\n\nquestion_id=[QUESTION_ID]&answer_id=[ANSWER_ID]&_wpnonce=[REST_NONCE]\n```\n\n## 6. Test Data Setup\n1. **Plugin Configuration**: Ensure LearnPress is active.\n2. **Content Creation**:\n   ```bash\n   # Create a question via WP-CLI (or use the UI)\n   # We need to manually add answers to ensure we have valid IDs\n   wp eval \"\n   \\$question_id = wp_insert_post(['post_title' => 'Vulnerable Question', 'post_type' => 'lp_question', 'post_status' => 'publish']);\n   update_post_meta(\\$question_id, '_lp_type', 'multi_choice');\n   global \\$wpdb;\n   \\$wpdb->insert(\\\"{\\$wpdb->prefix}learnpress_question_answers\\\", ['question_id' => \\$question_id, 'answer_data' => 'Ans 1', 'answer_order' => 1]);\n   \\$wpdb->insert(\\\"{\\$wpdb->prefix}learnpress_question_answers\\\", ['question_id' => \\$question_id, 'answer_data' => 'Ans 2', 'answer_order' => 2]);\n   \\$wpdb->insert(\\\"{\\$wpdb->prefix}learnpress_question_answers\\\", ['question_id' => \\$question_id, 'answer_data' => 'Ans 3', 'answer_order' => 3]);\n   echo 'Question ID: ' . \\$question_id;\n   \"\n   ```\n3. **Target Selection**: Query the database to get a valid `answer_id`.\n   ```bash\n   wp db query \"SELECT question_answer_id FROM wp_learnpress_question_answers WHERE answer_data = 'Ans 1'\"\n   ```\n4. **User Creation**:\n   ```bash\n   wp user create attacker attacker@example.com --role=subscriber --user_pass=password\n   ```\n\n## 7. Expected Results\n- **Response**: The server should return a JSON response (e.g., `{\"success\": true, \"data\": ...}`) or a simple `1`.\n- **Side Effect**: The row in `wp_learnpress_question_answers` corresponding to the targeted `answer_id` must be removed.\n\n## 8. Verification Steps\n1. **Database Check**: After the `http_request`, verify the answer is deleted using WP-CLI.\n   ```bash\n   wp db query \"SELECT * FROM wp_learnpress_question_answers WHERE question_answer_id = [ANSWER_ID]\"\n   ```\n   *Expected: No rows returned.*\n2. **UI Check**: Log in as Admin and view the Question Editor. One of the answer choices should be missing.\n\n## 9. Alternative Approaches\nIf `lp-ajax=delete_question_answer` does not resolve directly, try:\n- **Action Wrap**: `POST \u002Fwp-admin\u002Fadmin-ajax.php` with `action=lp_ajax&lp-ajax=delete_question_answer`.\n- **JSON Payload**: Some LP 4.x endpoints expect JSON. Try setting `Content-Type: application\u002Fjson` and sending `{\"question_id\": ..., \"answer_id\": ..., \"_wpnonce\": ...}`.\n- **REST Nonce Header**: If `_wpnonce` in the body is rejected, send the nonce via the `X-WP-Nonce` header.","The LearnPress plugin for WordPress lacks a capability check in its AJAX handler for deleting quiz question answers. This allows authenticated users with Subscriber-level permissions or higher to delete answer options from any quiz question on the site, provided they possess a valid REST API nonce.","--- a\u002Finc\u002FREvent\u002FAjax\u002FEditQuestionAjax.php\n+++ b\u002Finc\u002FREvent\u002FAjax\u002FEditQuestionAjax.php\n@@ -25,6 +25,10 @@\n \tpublic function delete_question_answer() {\n+\t\tif ( ! current_user_can( 'edit_lp_questions' ) ) {\n+\t\t\treturn;\n+\t\t}\n+\n \t\t$question_id = LP_Request::get_int( 'question_id' );\n \t\t$answer_id   = LP_Request::get_int( 'answer_id' );\n \t\t\n \t\tif ( ! $question_id || ! $answer_id ) {\n \t\t\treturn;","1. Authentication: Log in to the WordPress site as a Subscriber-level user (or any authenticated role).\n2. Nonce Acquisition: Extract the 'wp_rest' nonce from the frontend. This is commonly found in the JavaScript global variables 'lpData' or 'wpApiSettings' (e.g., window.lpData.nonce).\n3. Target Identification: Obtain the ID of the target Quiz Question (Post ID) and the specific Answer ID (from the 'learnpress_question_answers' database table) intended for deletion.\n4. Deletion Request: Send a POST request to the LearnPress AJAX dispatcher using the query parameter 'lp-ajax=delete_question_answer'.\n5. Payload Shape: Include the 'question_id', 'answer_id', and the extracted '_wpnonce' in the request body.\n6. Verification: Observe that the server processes the deletion despite the attacker lacking administrative or instructor privileges to edit questions.","gemini-3-flash-preview","2026-04-17 23:14:15","2026-04-17 23:14:44",{"type":41,"vulnerable_version":42,"fixed_version":11,"vulnerable_browse":43,"vulnerable_zip":44,"fixed_browse":45,"fixed_zip":46,"all_tags":47},"plugin","4.3.2.8","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Flearnpress\u002Ftags\u002F4.3.2.8","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Flearnpress.4.3.2.8.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Flearnpress\u002Ftags\u002F4.3.3","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Flearnpress.4.3.3.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Flearnpress\u002Ftags"]