WP-Optimize <= 4.5.2 - Authenticated (Author+) Arbitrary File Deletion via 'original-file' Post Meta
Description
The WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance plugin for WordPress is vulnerable to arbitrary file deletion due to insufficient file path validation in the unscheduled_original_file_deletion function in all versions up to, and including, 4.5.2 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). This is possible because 'original-file' is a public (non-protected) meta key — it does not begin with an underscore — allowing Authors to freely create or modify it on their own attachment posts via the standard Edit Media form or the REST API.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:HTechnical Details
What Changed in the Fix
Changes introduced in v4.5.3
Source Code
WordPress.org SVNThis research plan outlines the steps to verify the arbitrary file deletion vulnerability (CVE-2026-7252) in WP-Optimize versions up to 4.5.2. ### 1. Vulnerability Summary WP-Optimize features an image optimization (Smush) component that creates backups of original images before compression. The pl…
Show full research plan
This research plan outlines the steps to verify the arbitrary file deletion vulnerability (CVE-2026-7252) in WP-Optimize versions up to 4.5.2.
1. Vulnerability Summary
WP-Optimize features an image optimization (Smush) component that creates backups of original images before compression. The plugin uses a post meta key named original-file (associated with the attachment) to store the path of the original file.
Because original-file does not start with an underscore, WordPress treats it as a "public" meta key. This allows any user with edit_posts permissions (Author role and above) to modify this value via the REST API or the standard Media Edit form. The function unscheduled_original_file_deletion (likely called during a "Delete Backup" or "Undo Smush" operation) fails to validate that the path stored in this meta key resides within the expected uploads directory, allowing for arbitrary file deletion via path traversal.
2. Attack Vector Analysis
- Endpoint:
admin-ajax.php - Action:
wpo_smush_delete_backup(Inferred from CSS classwpo_smush_delete_backup_spinnerincss/smush-4-5-2.min.css) - Vulnerable Parameter: The
attachment_idsent to the AJAX action, which then retrieves theoriginal-filemeta. - Injection Point:
wp-json/wp/v2/media/<id>(REST API) to set theoriginal-filemeta value. - Authentication: Authenticated, Author-level access or higher.
- Preconditions:
- The attacker must have an Author account.
- The attacker must have uploaded at least one media file (to have an attachment ID to manipulate).
3. Code Flow (Inferred)
- Meta Injection: An Author sends a REST API request to update their attachment's metadata. Since
original-fileis public, WordPress allows the update. - Trigger: The Author triggers the
wpo_smush_delete_backupAJAX action for that specific attachment ID. - Retrieval: The AJAX handler calls
unscheduled_original_file_deletion($attachment_id). - Path Retrieval: The function calls
get_post_meta($attachment_id, 'original-file', true). - Sink: The retrieved path (e.g.,
../../../../wp-config.php) is passed directly to a deletion function (e.g.,wp_delete_file()orunlink()) without sufficient traversal checks.
4. Nonce Acquisition Strategy
The Smush actions in WP-Optimize are protected by nonces localized in the admin UI.
- Requirement: To see the Smush interface and get the nonce, the user must be in the Admin dashboard.
- Location: The nonces are typically found in the
wpo_smush_varsJavaScript object localized on the "WP-Optimize > Images" page. - Acquisition Steps:
- Log in as an Author.
- Navigate to the WP-Optimize Images page:
/wp-admin/admin.php?page=wp-optimize-images. - Use
browser_evalto extract the nonce:window.wpo_smush_vars?.delete_backup_nonce - If the above is unavailable, check
window.wp_optimize_ajax_vars?.nonce.
5. Exploitation Strategy
Step 1: Meta Injection (Path Traversal Setup)
As an Author, update the metadata of an image you own to point to wp-config.php.
- Request:
POST /wp-json/wp/v2/media/<ATTACHMENT_ID> - Headers:
Content-Type: application/json,X-WP-Nonce: <REST_NONCE> - Body:
(Note: The number of{ "meta": { "original-file": "../../../wp-config.php" } }../depends on the structure, but../../../wp-config.phpusually reaches the root from the year/month upload directory.)
Step 2: Trigger File Deletion
Call the AJAX action that processes the deletion.
- Request:
POST /wp-admin/admin-ajax.php - Headers:
Content-Type: application/x-www-form-urlencoded - Body:
action=wpo_smush_delete_backup&attachment_id=<ATTACHMENT_ID>&nonce=<DELETE_BACKUP_NONCE>
6. Test Data Setup
- User: Create a user
attackerwith theauthorrole. - File: As
attacker, upload a small PNG file to the Media Library. Note itsid(e.g.,123). - Target: Ensure
wp-config.phpexists in the WordPress root.
7. Expected Results
- The REST API call should return
200 OK, confirming the meta key was updated. - The AJAX call should return a success message (likely JSON with
success: true). - The file
wp-config.phpshould be deleted from the server.
8. Verification Steps
- File Existence: Check if the file still exists using the shell:
ls -la /var/www/html/wp-config.php - WP-CLI Check: Try to run a WP-CLI command. If
wp-config.phpis missing, it will fail:
Expected Error: "Error: The 'wp-config.php' file could not be found."wp option get siteurl
9. Alternative Approaches
- Absolute Path: If the plugin doesn't prepend the upload directory path, try an absolute path:
/var/www/html/wp-config.php. - Other Meta Keys: If
original-fileis patched, look for other public meta keys used in theUpdraft_Smush_Managerclass (referenced inchangelog.txt). - REST API Nonce: If the REST API is blocked for Authors, the meta can be updated via the
wp-admin/post.php(Edit Media) page using thecustom_metaor standard meta update fields if exposed.
Summary
The WP-Optimize plugin is vulnerable to authenticated arbitrary file deletion due to a path traversal flaw in the image optimization backup cleanup process. Because the 'original-file' post meta key is public, an attacker with Author-level permissions can modify this metadata to point to sensitive system files (like wp-config.php) and then trigger their deletion, potentially leading to a full site takeover or remote code execution.
Vulnerable Code
// includes/class-updraft-smush-manager.php:1644 public function unscheduled_original_file_deletion($post_id) { $the_original_file = get_post_meta($post_id, 'original-file', true); $uploads_dir = wp_get_upload_dir(); $the_original_file = trailingslashit($uploads_dir['basedir']) . $the_original_file; if ('' != $the_original_file && file_exists($the_original_file)) { wp_delete_file($the_original_file); } }
Security Fix
@@ -83,6 +83,8 @@ add_filter('manage_media_columns', array($this, 'manage_media_columns')); add_action('manage_media_custom_column', array($this, 'manage_media_custom_column'), 10, 2); + add_filter('is_protected_meta', array($this, 'is_protected_meta'), 10, 3); + // clean backup images cron action. add_action('wpo_smush_clear_backup_images', array($this, 'clear_backup_images')); @@ -376,12 +378,20 @@ $image_path = get_attached_file($image_id); $backup_path = get_post_meta($image_id, 'original-file', true); - + $uploads_dir = wp_upload_dir(); + $uploads_basedir = realpath($uploads_dir['basedir']); + + if (false === $uploads_basedir) { + if ($switched_blog) { + restore_current_blog(); + } + return new WP_Error('restore_backup_issue', __('The uploads base directory is not correct.', 'wp-optimize')); + } + + $uploads_basedir = trailingslashit($uploads_basedir); + // If the file doesn't exist, check if it's relative if (!is_file($backup_path)) { - $uploads_dir = wp_upload_dir(); - $uploads_basedir = trailingslashit($uploads_dir['basedir']); - if (is_file($uploads_basedir . $backup_path)) { $backup_path = $uploads_basedir . $backup_path; } @@ -412,6 +422,15 @@ } } + $backup_path = realpath($backup_path); + + if (!$backup_path || 0 !== strpos($backup_path, $uploads_basedir)) { + if ($switched_blog) { + restore_current_blog(); + } + return new WP_Error('restore_backup_issue', __('The backup file path is not correct.', 'wp-optimize')); + } + if (!is_file($backup_path)) { // Delete information about backup. delete_post_meta($image_id, 'original-file'); @@ -1644,8 +1663,11 @@ public function unscheduled_original_file_deletion($post_id) { $the_original_file = get_post_meta($post_id, 'original-file', true); $uploads_dir = wp_get_upload_dir(); - $the_original_file = trailingslashit($uploads_dir['basedir']) . $the_original_file; - if ('' != $the_original_file && file_exists($the_original_file)) { + $uploads_basedir = realpath($uploads_dir['basedir']); + if (!$uploads_basedir) return; + $uploads_basedir = trailingslashit($uploads_basedir); + $the_original_file = realpath($uploads_basedir . $the_original_file); + if ($the_original_file && 0 === strpos($the_original_file, $uploads_basedir) && file_exists($the_original_file)) { wp_delete_file($the_original_file); } } @@ -1736,6 +1758,36 @@ ), ); } + + /** + * Protects smush meta keys + * + * @param bool $protected Whether the key is considered protected + * @param string $meta_key Metadata key + * @param string $meta_type Type of metadata object + * + * @return bool + */ + public function is_protected_meta($protected, $meta_key, $meta_type) { + if ('post' !== $meta_type) { + return $protected; + } + + $keys = array( + 'smush-complete', + 'smush-marked', + 'smush-info', + 'smush-stats', + 'original-file', + 'wpo-webp-conversion-complete', + ); + + if (in_array($meta_key, $keys, true)) { + return true; + } + + return $protected; + } }
Exploit Outline
To exploit this vulnerability, an attacker with Author-level access (or higher) must first upload a media file to obtain an attachment ID. They then send a REST API request to `POST /wp-json/wp/v2/media/<ID>` containing a JSON payload that sets the `meta` key `original-file` to a path traversal string (e.g., `../../../wp-config.php`). Finally, the attacker triggers the file deletion by invoking the `wpo_smush_delete_backup` AJAX action via `admin-ajax.php`, providing the attachment ID and a valid security nonce (retrieved from the WP-Optimize admin UI). The plugin then deletes the file specified in the metadata without validating that it is located within the intended uploads directory.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.