[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fw-ClQ2PLPDNFhv2Afe8apRdFIaqvReU7KXXdKMXfowY":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":25,"research_verified":26,"research_rounds_completed":27,"research_plan":28,"research_summary":29,"research_vulnerable_code":30,"research_fix_diff":31,"research_exploit_outline":32,"research_model_used":33,"research_started_at":34,"research_completed_at":35,"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":26,"poc_model_used":9,"poc_verification_depth":9,"poc_exploit_code_gated":26,"source_links":36},"CVE-2026-11397","wp-import-export-lite-authenticated-administrator-server-side-request-forgery-via-fileurl-parameter","WP Import Export Lite \u003C= 3.9.30 - Authenticated (Administrator+) Server-Side Request Forgery via 'file_url' Parameter","The WP Import Export Lite plugin for WordPress is vulnerable to Server-Side Request Forgery in all versions up to and including 3.9.30 via the wpie_import_upload_file_from_url AJAX action. The plugin's URL downloader first calls wp_safe_remote_get() (which correctly blocks private\u002Freserved IP ranges), but when that call returns a WP_Error — the exact outcome for any blocked internal host — the Download::download_file() method falls back to GuzzleHttp\\Client::request() with the original attacker-supplied URL and no SSRF protection (and with TLS verification disabled). This makes it possible for authenticated attackers, with administrator-level access and above, to make web requests to arbitrary locations originating from the web application and can be used to query and modify information from internal services such as the cloud metadata endpoint at 169.","wp-import-export-lite",null,"\u003C=3.9.30","3.9.31","medium",5.5,"CVSS:3.1\u002FAV:N\u002FAC:L\u002FPR:H\u002FUI:N\u002FS:C\u002FC:L\u002FI:L\u002FA:N","Server-Side Request Forgery (SSRF)","2026-07-02 16:07:10","2026-07-03 04:30:18",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002F94384812-fa6e-48db-a84a-b1769e62ca58?source=api-prod",1,[22,23,24],"includes\u002Fclasses\u002Fimport\u002Fdownloader\u002Fdownload.php","readme.txt","wp-import-export-lite.php","researched",false,3,"This exploitation research plan focuses on **CVE-2026-11397**, an authenticated SSRF vulnerability in the **WP Import Export Lite** plugin. The vulnerability arises from an insecure fallback mechanism in the file downloader class that bypasses WordPress's built-in SSRF protections.\n\n### 1. Vulnerability Summary\nThe `WP Import Export Lite` plugin provides functionality to download files from a URL for import purposes. The core logic resides in the `wpie\\import\\Downloader\\Download` class. \n\nThe vulnerability exists because `download_file()` first attempts to use `wp_safe_remote_get()`, which correctly validates URLs against internal and reserved IP ranges. However, if `wp_safe_remote_get()` returns a `WP_Error` (which is the expected behavior when a request to a blocked internal IP is attempted), the code catches this error and falls back to a second method: `guzzle_download()`. \n\nThe `guzzle_download()` method uses the `GuzzleHttp\\Client` library to fetch the **same** attacker-supplied URL without any SSRF filtering and with SSL verification explicitly disabled (`'verify' => false`). This allows an administrator to probe internal network services (e.g., `127.0.0.1`, `169.254.169.254`) that are otherwise protected by WordPress.\n\n### 2. Attack Vector Analysis\n*   **Endpoint:** `\u002Fwp-admin\u002Fadmin-ajax.php`\n*   **AJAX Action:** `wpie_import_upload_file_from_url`\n*   **Vulnerable Parameter:** `file_url`\n*   **Authentication Required:** Administrator (or any user with access to the plugin's import functionality).\n*   **Preconditions:** The plugin must be active.\n\n### 3. Code Flow\n1.  The attacker sends an AJAX request with `action=wpie_import_upload_file_from_url` and a `file_url` pointing to an internal resource (e.g., `http:\u002F\u002F169.254.169.254\u002Flatest\u002Fmeta-data\u002F`).\n2.  The AJAX handler (typically in `includes\u002Fclasses\u002Fimport\u002Fclass-wpie-import-handler.php`, inferred) instantiates `wpie\\import\\Downloader\\Download` and calls `download_file($_POST['file_url'])`.\n3.  **`Download::download_file()`** (in `includes\u002Fclasses\u002Fimport\u002Fdownloader\u002Fdownload.php`):\n    *   Calls `$this->wp_download()`.\n4.  **`Download::wp_download()`**:\n    *   Calls `wp_safe_remote_get($this->url, ...)`.\n    *   `wp_safe_remote_get` identifies the URL as an internal\u002Fprivate IP and returns a `WP_Error`.\n    *   `wp_download()` returns this `WP_Error` to the caller.\n5.  **`Download::download_file()`** (resume):\n    *   Detects `is_wp_error($wp_file)`.\n    *   Calls `$this->guzzle_download()`.\n6.  **`Download::guzzle_download()`**:\n    *   Instantiates `GuzzleHttp\\Client`.\n    *   Executes `$client->request('GET', $this->url, ['sink' => $file, 'verify' => false])`.\n    *   **Sink:** The internal data is written to a temporary file: `get_temp_dir() . time() . rand() . \".tmp\"`.\n    *   Returns the path to the temporary file.\n\n### 4. Nonce Acquisition Strategy\nThe `wpie_import_upload_file_from_url` action is protected by a nonce. Based on the plugin structure, this nonce is usually localized for the import screen.\n\n1.  **Identify Trigger:** The nonce is required for the \"Upload from URL\" feature in the Import menu.\n2.  **Navigation:** Navigate to the Import page: `\u002Fwp-admin\u002Fadmin.php?page=wpie-import`.\n3.  **Extraction:**\n    *   The plugin localizes its data using `wp_localize_script`.\n    *   Use `browser_eval` to extract the nonce from the global JavaScript object.\n    *   **Inferred Variable:** The object is likely `wpie_import_obj` or `wpie_import_vars`.\n    *   **Command:** `browser_eval(\"window.wpie_import_obj?.wpie_nonce\")` or `browser_eval(\"window.wpie_import_vars?.nonce\")`.\n4.  **Action Verification:** The nonce is likely created with the action string `'wpie_import_nonce'`. If `wp_verify_nonce` in the handler uses a different string or `-1`, adjust accordingly.\n\n### 5. Exploitation Strategy\nThe exploit involves forcing the plugin to download data from an internal service and then confirming the data was written to the filesystem.\n\n**Step 1: Perform the SSRF**\nSend a POST request to `admin-ajax.php`:\n*   **URL:** `http:\u002F\u002F\u003Ctarget>\u002Fwp-admin\u002Fadmin-ajax.php`\n*   **Method:** POST\n*   **Headers:** `Content-Type: application\u002Fx-www-form-urlencoded`\n*   **Body:**\n    ```\n    action=wpie_import_upload_file_from_url&wpie_nonce=[NONCE]&file_url=http:\u002F\u002F169.254.169.254\u002Flatest\u002Fmeta-data\u002F\n    ```\n\n**Step 2: Parse Response**\nThe response is typically a JSON object. Look for a `success` key and a `file_name` or `file_path` indicating where the internal data was saved.\n*   **Example Response:** `{\"success\": true, \"data\": {\"file_name\": \"171543210012345.tmp\", ...}}`\n\n**Step 3: Access Internal Data**\nSince the file is saved in the directory returned by `get_temp_dir()`, it is often located in `wp-content\u002Fuploads\u002Fwp-import-export-lite\u002Ftemp\u002F` (defined as `WPIE_UPLOAD_TEMP_DIR` in `wp-import-export-lite.php`).\n\n### 6. Test Data Setup\n1.  Install and activate **WP Import Export Lite \u003C= 3.9.30**.\n2.  Create an administrator user.\n3.  Ensure the `wp-content\u002Fuploads\u002Fwp-import-export-lite\u002Ftemp\u002F` directory is writable.\n4.  (Optional) For local testing, set up a simple internal service: `python3 -m http.server 8080 --bind 127.0.0.1`.\n\n### 7. Expected Results\n*   The AJAX request should return `success: true`.\n*   A `.tmp` file should be created in the WordPress temporary directory or the plugin's temp directory.\n*   The content of the `.tmp` file should contain the response from the internal service (e.g., AWS metadata or the local Python server's directory listing).\n\n### 8. Verification Steps (Post-Exploit)\nUse `wp-cli` to verify the presence and content of the leaked data:\n```bash\n# List files in the plugin temp directory\nwp eval \"print_r(glob(WPIE_UPLOAD_TEMP_DIR . '\u002F*.tmp'));\"\n\n# Read the content of the most recent file\nwp eval \"echo file_get_contents(WPIE_UPLOAD_TEMP_DIR . '\u002F[FILENAME_FROM_AJAX_RESPONSE]');\"\n```\n\n### 9. Alternative Approaches\n*   **Gopher\u002FDict Protocols:** If Guzzle is configured with full cURL support, try `gopher:\u002F\u002F` or `dict:\u002F\u002F` to interact with services like Redis or Memcached.\n*   **Local File Read (Inferred):** Check if `file_url` supports the `file:\u002F\u002F` scheme. While `wp_safe_remote_get` blocks it, Guzzle's behavior depends on the underlying handler; however, Guzzle typically only supports HTTP\u002FHTTPS by default.\n*   **Blind SSRF\u002FOOB:** If the internal service doesn't return data, use a collaborator URL (e.g., Burp Collaborator) to verify the request originates from the server's IP, bypassing any outbound firewall rules that might allow only the web server's traffic.","The WP Import Export Lite plugin for WordPress is vulnerable to authenticated Server-Side Request Forgery (SSRF) due to an insecure fallback mechanism in its file downloader. When the plugin's initial attempt to fetch a URL via wp_safe_remote_get() is blocked (as happens with internal or reserved IP ranges), the code catches the resulting error and retries the request using GuzzleHttp with no SSRF protections and SSL verification disabled. This allows administrators to bypass WordPress security restrictions and interact with internal network services or cloud metadata endpoints.","\u002F* includes\u002Fclasses\u002Fimport\u002Fdownloader\u002Fdownload.php:24 *\u002F\n                $wp_file = $this->wp_download();\n\n                if ( is_wp_error( $wp_file ) ) {\n\n                        $guzzle_file = $this->guzzle_download();\n\n                        if ( !is_wp_error( $guzzle_file ) ) {\n                                $wp_file = $guzzle_file;\n                        }\n                }\n\n---\n\n\u002F* includes\u002Fclasses\u002Fimport\u002Fdownloader\u002Fdownload.php:86 *\u002F\n        private function guzzle_download() {\n\n                $filename = time() . rand() . \" .tmp\";\n\n                $file = get_temp_dir() . $filename;\n\n                \\wpie_load_vendor_autoloader();\n\n                try {\n                        $client = new \\GuzzleHttp\\Client();\n\n                        $response = $client->request( 'GET', $this->url, [ 'sink' => $file, 'verify' => false ] );\n                } catch ( \\Exception $e ) {\n                        return new \\WP_Error( 'download_error', $e->getMessage() );\n                }","--- \u002Fincludes\u002Fclasses\u002Fimport\u002Fdownloader\u002Fdownload.php\n+++ \u002Fincludes\u002Fclasses\u002Fimport\u002Fdownloader\u002Fdownload.php\n@@ -17,11 +17,15 @@\n-        public function download_file( $url = \"\" ) {\n+        public function download_file($url = \"\")\n+        {\n+\n+                if (empty($url)) {\n+                        return new \\WP_Error('wpie_import_error', __('File Download Error : File URL is empty', 'wp-import-export-lite'));\n+                }\n \n-                $this->url = $url;\n+                $this->url = \\wp_http_validate_url($url);\n \n-                if ( empty( $this->url ) ) {\n-                        return new \\WP_Error( 'wpie_import_error', __( 'File Download Error : File URL is empty', 'wp-import-export-lite' ) );\n+                if (false === $this->url) {\n+                        return new \\WP_Error('wpie_import_error', __('File Download Error : File URL is not valid', 'wp-import-export-lite'));\n                 }","The exploit is performed by an authenticated administrator using the plugin's file import functionality. 1. Access the WordPress dashboard and navigate to the plugin's Import screen to obtain a valid nonce (usually located in the localized 'wpie_import_obj' JavaScript object). 2. Submit a POST request to '\u002Fwp-admin\u002Fadmin-ajax.php' with the 'action' parameter set to 'wpie_import_upload_file_from_url' and the 'file_url' parameter set to a target internal resource (e.g., 'http:\u002F\u002F169.254.169.254\u002Flatest\u002Fmeta-data\u002F'). 3. The plugin will first fail the safe request via wp_safe_remote_get but will immediately fallback to an unrestricted Guzzle request. 4. The internal data is written to a .tmp file in the plugin's temporary directory ('wp-content\u002Fuploads\u002Fwp-import-export-lite\u002Ftemp\u002F'). 5. The attacker can then retrieve the leaked content by checking the filename returned in the AJAX JSON response and accessing that file on the server.","gemini-3-flash-preview","2026-07-25 09:54:15","2026-07-25 09:55:13",{"type":37,"vulnerable_version":38,"fixed_version":11,"vulnerable_browse":39,"vulnerable_zip":40,"fixed_browse":41,"fixed_zip":42,"all_tags":43},"plugin","3.9.30","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fwp-import-export-lite\u002Ftags\u002F3.9.30","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fwp-import-export-lite.3.9.30.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fwp-import-export-lite\u002Ftags\u002F3.9.31","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fwp-import-export-lite.3.9.31.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fwp-import-export-lite\u002Ftags"]