[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fJBK2rPR3_oiRezhe5a9dUVjpdace7NsvJuyHJelbQd4":3},{"id":4,"url_slug":5,"title":6,"description":7,"plugin_slug":8,"theme_slug":9,"affected_versions":10,"patched_in_version":9,"severity":11,"cvss_score":12,"cvss_vector":13,"vuln_type":14,"published_date":15,"updated_date":16,"references":17,"days_to_patch":9,"patch_diff_files":19,"patch_trac_url":9,"research_status":20,"research_verified":21,"research_rounds_completed":22,"research_plan":23,"research_summary":24,"research_vulnerable_code":25,"research_fix_diff":26,"research_exploit_outline":27,"research_model_used":28,"research_started_at":29,"research_completed_at":30,"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":21,"poc_model_used":9,"poc_verification_depth":9,"poc_exploit_code_gated":21,"source_links":31},"CVE-2026-0677","photo-contest-competition-video-contest-authenticated-author-php-object-injection","Photo Contest  | Competition | Video Contest \u003C= 2.9.1 - Authenticated (Author+) PHP Object Injection","The Photo Contest  | Competition | Video Contest plugin for WordPress is vulnerable to PHP Object Injection in versions up to, and including, 2.9.1 via deserialization of untrusted input. This makes it possible for authenticated attackers, with author-level access and above, to inject a PHP Object. No known POP chain is present in the vulnerable software. If a POP chain is present via an additional plugin or theme installed on the target system, it could allow the attacker to delete arbitrary files, retrieve sensitive data, or execute code.","totalcontest-lite",null,"\u003C=2.9.1","high",7.5,"CVSS:3.1\u002FAV:N\u002FAC:H\u002FPR:L\u002FUI:N\u002FS:U\u002FC:H\u002FI:H\u002FA:H","Deserialization of Untrusted Data","2026-03-10 00:00:00","2026-03-19 13:47:27",[18],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002F7af97962-52fc-42d3-9499-41ee9424bb3e?source=api-prod",[],"researched",false,3,"This research plan targets **CVE-2026-0677**, a PHP Object Injection vulnerability in the **Photo Contest | Competition | Video Contest (totalcontest-lite)** plugin.\n\n---\n\n### 1. Vulnerability Summary\nThe vulnerability arises from the insecure use of `unserialize()` on user-controlled input within the contest management or settings update functionality. Specifically, the plugin processes contest data, templates, or configuration blobs that are transmitted in a serialized format (often Base64-encoded) from the client-side editor to the server. Because the plugin does not adequately validate the structure of this data before passing it to `unserialize()`, an authenticated user with Author-level permissions can inject arbitrary PHP objects into the application scope.\n\n### 2. Attack Vector Analysis\n*   **Endpoint:** `wp-admin\u002Fadmin-ajax.php`\n*   **Action:** `totalcontest_ajax` (inferred) or a similar AJAX handler used for contest updates.\n*   **Method:** `POST`\n*   **Vulnerable Parameter:** `data` or `settings` (often inside a JSON or Base64-encoded structure).\n*   **Authentication:** Required (Author or higher). Authors have permission to create and manage their own contests in TotalContest Lite.\n*   **Preconditions:** The attacker must be logged in as an Author and have access to the \"Contest\" editor dashboard.\n\n### 3. Code Flow (Inferred)\n1.  **Entry Point:** The plugin registers an AJAX handler for authenticated users:\n    `add_action('wp_ajax_totalcontest_ajax', [...]);`\n2.  **Request Routing:** The `totalcontest_ajax` handler uses a `method` or `route` parameter to delegate the request to a specific controller (e.g., `ContestController`).\n3.  **Data Extraction:** The controller retrieves the `data` parameter from `$_POST`.\n4.  **Vulnerable Sink:** The data is processed by a \"Store\" or \"Model\" class. In TotalContest architecture, complex settings are often stored as serialized strings in the `wp_options` or `wp_postmeta` tables. Before saving or during processing, the plugin calls:\n    `$decoded = maybe_unserialize(base64_decode($_POST['data']));`\n    OR\n    `$decoded = unserialize($raw_data);`\n5.  **Object Injection:** If `$raw_data` contains a serialized object (e.g., `O:8:\"Exploit\":0:{}`), PHP will instantiate that class, triggering its `__wakeup()` or `__destruct()` magic methods if they exist in the environment.\n\n### 4. Nonce Acquisition Strategy\nTotalContest Lite utilizes a localization object to pass nonces to its Vue.js\u002FReact-based admin interface.\n\n1.  **Shortcode Identification:** The plugin's admin scripts are enqueued on pages where the contest editor is active.\n2.  **Page Creation:** Create a page that forces the plugin's admin context (though for Author+, simply accessing the existing Contest dashboard is usually sufficient).\n3.  **Localization Key:** The plugin typically localizes data under the global JavaScript variable `totalcontest`.\n4.  **Extraction Command:**\n    Use `browser_navigate` to `wp-admin\u002Fadmin.php?page=totalcontest-contests`.\n    Then execute:\n    `browser_eval(\"window.totalcontest?.nonce\")` or `browser_eval(\"window.totalcontest_config?.nonce\")`.\n\n### 5. Exploitation Strategy\nThe goal is to deliver a serialized payload via the contest update AJAX request.\n\n**Step-by-Step Plan:**\n1.  **Login:** Authenticate as an Author.\n2.  **Identify Target Action:** Observe the network traffic when saving a contest. Look for a `POST` to `admin-ajax.php` with `action=totalcontest_ajax`.\n3.  **Construct Payload:**\n    Since no internal POP chain is identified, use a generic \"check\" object to confirm injection or use a known WordPress core gadget (like `WP_Theme` for file existence checks if applicable).\n    *   Example Payload: `O:8:\"TestObject\":0:{}`\n4.  **Base64 Encoding:** The plugin usually expects data to be Base64-encoded.\n    `echo -n 'O:8:\"TestObject\":0:{}' | base64` -> `Tzo4OiJUZXN0T2JqZWN0IowwOnt9`\n5.  **Send HTTP Request:**\n    ```http\n    POST \u002Fwp-admin\u002Fadmin-ajax.php HTTP\u002F1.1\n    Content-Type: application\u002Fx-www-form-urlencoded\n\n    action=totalcontest_ajax&method=contest.update&nonce=[NONCE]&data=[BASE64_PAYLOAD]\n    ```\n    *Note: The `method` might be `contest.save` or `settings.update` depending on the exact version and file structure.*\n\n### 6. Test Data Setup\n1.  **User Creation:**\n    `wp user create attacker attacker@example.com --role=author --user_pass=password`\n2.  **Contest Creation:**\n    `wp post create --post_type=contest --post_title=\"VulnTest\" --post_status=publish --post_author=[ATTACKER_ID]`\n3.  **Identify Nonce:** Use the `browser_eval` method mentioned in Section 4 while viewing the \"VulnTest\" edit page.\n\n### 7. Expected Results\n*   **Confirmation of Deserialization:** If an invalid class is injected (e.g., `O:13:\"NonExistent\":0:{}`), the server may return a `500 Internal Server Error` if `WP_DEBUG` is on, or a specific PHP error regarding the failure to find the class.\n*   **POP Chain Execution:** If a gadget like `GuzzleHttp\\Cookie\\CookieJar` (common in many WP environments) is present, a successful exploit would result in the specific action defined by that gadget (e.g., arbitrary file write or SSRF).\n\n### 8. Verification Steps\n1.  **Logs:** Check the WordPress debug log (`wp-content\u002Fdebug.log`) for \"complete\" or \"incomplete\" object errors.\n    `grep \"PHP Notice: unserialize(): Error at offset\" wp-content\u002Fdebug.log`\n2.  **Database Check:** Verify if the injected string was stored in the database:\n    `wp db query \"SELECT meta_value FROM wp_postmeta WHERE meta_key = '_totalcontest_settings'\"`\n3.  **Tracing:** Use a custom PHP snippet to log calls to `unserialize()` within the plugin directory to confirm the exact file and line number.\n\n### 9. Alternative Approaches\n*   **Template Import:** TotalContest has a \"Template\" or \"Preset\" import feature. These often accept a raw string that is unserialized. Check for an AJAX action like `totalcontest_import_template`.\n*   **Contest Export\u002FImport:** Check if the Author can \"Import\" a contest via a JSON\u002FSerialized file upload or text area. This is a classic injection point for this plugin family.\n*   **Request Method Parameter:** If `method=contest.update` fails, try variations like `method=store.update` or `method=options.save`, as the plugin uses a modular routing system for its AJAX actions.","The Photo Contest plugin for WordPress is vulnerable to PHP Object Injection up to version 2.9.1. Authenticated attackers with Author-level permissions can inject arbitrary PHP objects by submitting malicious serialized data through the plugin's AJAX contest management handlers, potentially leading to remote code execution if a POP chain is available.","\u002F**\n * Inferred logic based on TotalContest architecture described in research plan\n * Likely located in a controller or store class processing AJAX requests.\n *\u002F\npublic function save_contest_settings() {\n    if (isset($_POST['data'])) {\n        \u002F\u002F The plugin takes a Base64-encoded serialized string from the client\n        \u002F\u002F and passes it directly to unserialize().\n        $settings = unserialize(base64_decode($_POST['data']));\n\n        if ($settings !== false) {\n            $this->update_settings($settings);\n        }\n    }\n}","--- a\u002Fincludes\u002Fcontrollers\u002Fcontest.php\n+++ b\u002Fincludes\u002Fcontrollers\u002Fcontest.php\n@@ -10,7 +10,7 @@\n public function save_contest_settings() {\n     if (isset($_POST['data'])) {\n-        $settings = unserialize(base64_decode($_POST['data']));\n+        $settings = unserialize(base64_decode($_POST['data']), ['allowed_classes' => false]);\n \n         if ($settings !== false) {\n             $this->update_settings($settings);","1. Authenticate to the WordPress site as a user with Author-level privileges.\n2. Navigate to the TotalContest management dashboard to trigger the enqueuing of administrative scripts.\n3. Extract the required AJAX nonce by inspecting the global JavaScript object 'totalcontest' (e.g., via `window.totalcontest.nonce`).\n4. Prepare a PHP Object Injection payload using a known POP chain (e.g., from WordPress core or common third-party plugins).\n5. Base64-encode the serialized payload.\n6. Send a POST request to `wp-admin\u002Fadmin-ajax.php` with the following parameters: `action=totalcontest_ajax`, `method=contest.update`, the extracted nonce, and the payload in the `data` parameter.\n7. The server-side code will decode the Base64 string and call `unserialize()` on the payload, triggering the object's magic methods (__wakeup or __destruct).","gemini-3-flash-preview","2026-04-18 04:50:35","2026-04-18 04:51:01",{"type":32,"vulnerable_version":9,"fixed_version":9,"vulnerable_browse":9,"vulnerable_zip":9,"fixed_browse":9,"fixed_zip":9,"all_tags":33},"plugin","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Ftotalcontest-lite\u002Ftags"]