Enable Media Replace <= 4.1.7 - Improper Authorization to Authenticated (Author+) Arbitrary Attachment Change via Background Replace
Description
The Enable Media Replace plugin for WordPress is vulnerable to unauthorized modification of data due to an improper capability check on the 'RemoveBackGroundViewController::load' function in all versions up to, and including, 4.1.7. This makes it possible for authenticated attackers, with Author-level access and above, to replace any attachment with a removed background attachment.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:LTechnical Details
<=4.1.7What Changed in the Fix
Changes introduced in v4.1.8
Source Code
WordPress.org SVN# Exploitation Research Plan - CVE-2026-2732 ## 1. Vulnerability Summary The **Enable Media Replace** plugin for WordPress is vulnerable to improper authorization in its "Background Replace" feature. Specifically, the `EnableMediaReplace\ViewController\RemoveBackGroundViewController::load` and `loa…
Show full research plan
Exploitation Research Plan - CVE-2026-2732
1. Vulnerability Summary
The Enable Media Replace plugin for WordPress is vulnerable to improper authorization in its "Background Replace" feature. Specifically, the EnableMediaReplace\ViewController\RemoveBackGroundViewController::load and loadPost functions do not verify if the current user has the authority to modify a specific attachment.
While the code checks for the upload_files capability (held by Authors and above), it fails to check the edit_post capability for the specific attachment_id being manipulated. This allows an authenticated user with Author-level access to replace any attachment on the site (including those owned by Administrators) with an image processed through the background removal service.
2. Attack Vector Analysis
- Endpoint:
wp-admin/upload.php?page=enable-media-replace/enable-media-replace.php - Action:
emr_prepare_remove(to get the nonce/UI) and the subsequent POST request handled byloadPost. - Vulnerable Params:
ID(The attachment ID to be replaced) andkey(The identifier for the processed image). - Authentication: Authenticated, Author-level role (capability
upload_files). - Preconditions:
- A target attachment ID owned by an Administrator must exist.
- The attacker must have at least one attachment of their own to generate a valid
keyandnoncefrom the background removal flow.
3. Code Flow
- Entry Point: The plugin registers a submenu page in
classes/emr-plugin.php:add_submenu_page('upload.php', $title, $title, 'upload_files', 'enable-media-replace/enable-media-replace.php', array($this, 'route')); - Routing: The
routemethod determines whichViewControllerto load. For background removal, it invokesRemoveBackGroundViewController. - Missing Permission Check (GET): In
classes/ViewController/RemoveBackgroundViewController.php, theload()function:public function load() { if (!current_user_can('upload_files')) { // Author+ check $this->viewError(self::ERROR_UPLOAD_PERMISSION); } $attachment_id = intval($_REQUEST['attachment_id']); // No check if user can edit this ID // ... } - Processing (POST): When the "Replace" button is clicked after background removal,
loadPost()is called:
At no point inpublic function loadPost() { if (!isset($_POST['emr_nonce']) || !wp_verify_nonce($_POST['emr_nonce'], 'media_remove_background')) { $this->viewError(self::ERROR_NONCE); } $post_id = isset($_POST['ID']) ? intval($_POST['ID']) : null; // Target ID from user input $key = isset($_POST['key']) ? sanitize_text_field($_POST['key']) : null; // Result key from API $result = $this->replaceBackground($post_id, $key); // Downloads processed image $replaceController = new ReplaceController($post_id); // ... sets up params ... $result = $replaceController->run(); // Overwrites target attachment }loadPostis there a check likecurrent_user_can('edit_post', $post_id).
4. Nonce Acquisition Strategy
The nonce media_remove_background is required for the loadPost action.
- Target Identification: Find an
attachment_idowned by the Admin. - Nonce Extraction:
- Create a dummy attachment owned by the Author.
- Navigate to the background removal preparation page for the Author's own image:
wp-admin/upload.php?page=enable-media-replace/enable-media-replace.php&action=emr_prepare_remove&attachment_id=[AUTHOR_ID] - The nonce is generated in the view
prepare-remove-background. - Use
browser_evalto extract it:document.querySelector('input[name="emr_nonce"]').value
- Key Acquisition:
- The
keyidentifies the background-removed image processed by the ShortPixel API. - The attacker can initiate a legitimate removal on their own image. When the API returns the result, the
keyis populated in the form. Thiskeycan then be repurposed against the Admin'sID.
- The
5. Exploitation Strategy
- Login as a user with the
Authorrole. - Prepare Target: Identify an Admin-owned attachment (e.g., ID
10). - Prepare Payload: Upload a simple image as Author (e.g., ID
11). - Initiate Process: Start the "Remove Background" flow for ID
11. - Intercept/Capture: Capture the
emr_nonceand thekeyreturned by the ShortPixel background removal service. (Thekeyis typically returned via AJAX or updated in the UI after the image is processed). - Execute Exploit: Submit a POST request with the captured
keybut pointing to the Admin's attachmentID.
Request Details:
- URL:
http://localhost:8080/wp-admin/upload.php?page=enable-media-replace%2Fenable-media-replace.php - Method: POST
- Headers:
Content-Type: application/x-www-form-urlencoded - Body:
(Note: The exactemr_nonce=[CAPTURED_NONCE]& ID=10& key=[CAPTURED_KEY]& action=emr_remove_backgroundactionparameter may vary depending on how the plugin's router handles theloadPosttransition, but typically it follows the submenu page logic).
6. Test Data Setup
- Users:
admin_user(Administrator)author_user(Author)
- Attachments:
admin_img.jpguploaded byadmin_user(Target ID).author_img.jpguploaded byauthor_user(Attacker-controlled source).
7. Expected Results
- The plugin will successfully process the request because the Author has
upload_filescapability. - The
ReplaceControllerwill fetch the image associated with thekey(which the Author generated) and overwrite the file associated with the Admin's attachmentID. - The Admin's image is now replaced by the Author's background-removed image.
8. Verification Steps
- Check Attachment Owner:
wp post get [ADMIN_ID] --field=post_author(Should still be Admin). - Check File Path:
wp post get [ADMIN_ID] --field=guid - Verify File Content: Check if the file on disk for the Admin's attachment has changed to the background-removed version of the Author's image.
- Check Meta:
wp post meta get [ADMIN_ID] _emr_replace_author
(InReplaceController::run(), the plugin explicitly tracks the replacer:update_post_meta($this->post_id, '_emr_replace_author', get_current_user_id());. This meta will confirm the Author ID replaced it.)
9. Alternative Approaches
If the key is tied strictly to the original attachment_id within the API side, the attacker can try to manipulate the ID parameter during the initial request to the background removal service. However, the loadPost logic in the plugin suggests it accepts any ID alongside a valid key and nonce.
If the background removal API is unavailable, the exploit can be proven by mocking the replaceBackground result to return a local path, demonstrating that the ReplaceController proceeds to overwrite any ID without checking if the user is the owner.
Summary
The Enable Media Replace plugin for WordPress (<= 4.1.7) fails to perform granular authorization checks in its background removal feature. Authenticated users with Author-level access can replace any attachment on the site—including those owned by administrators—by manipulating the attachment ID in the background replacement workflow.
Vulnerable Code
// classes/ViewController/RemoveBackgroundViewController.php line 40 public function load() { if (!current_user_can('upload_files')) { $this->viewError(self::ERROR_UPLOAD_PERMISSION); // wp_die(esc_html__('You do not have permission to upload files.', 'enable-media-replace')); } $attachment_id = intval($_REQUEST['attachment_id']); --- // classes/ViewController/RemoveBackgroundViewController.php line 86 $post_id = isset($_POST['ID']) ? intval($_POST['ID']) : null; // sanitize, post_id. if (is_null($post_id)) { $this->viewError(self::ERROR_FORM); // wp_die(esc_html__('Error in request. Please try again', 'enable-media-replace')); } $this->setView($post_id); $result = $this->replaceBackground($post_id, $key); if (false === $result->success) { $this->view->errorMessage = $result->message; $this->viewError(); } elseif (! file_exists($result->image)) { $this->viewError(self::ERROR_DOWNLOAD_FAILED); } // $result = $replacer->replaceWith($result->image, $source->getFileName() , true); //$params = array(); $replaceController = new ReplaceController($post_id); $sourceFile = $replaceController->getSourceFile();
Security Fix
@@ -37,10 +37,15 @@ // wp_die(esc_html__('You do not have permission to upload files.', 'enable-media-replace')); } - $attachment_id = intval($_REQUEST['attachment_id']); $attachment = get_post($attachment_id); + if (! \emr()->checkImagePermission($attachment)) + { + $this->viewError(self::ERROR_IMAGE_PERMISSION); + wp_die( esc_html__('You do not have permission to upload files for this author.', 'enable-media-replace') ); + } + $uiHelper = \emr()->uiHelper(); $uiHelper->setPreviewSizes(); $uiHelper->setSourceSizes($attachment_id); @@ -77,30 +82,34 @@ if (is_null($key) || strlen($key) == 0) { $this->viewError(self::ERROR_KEY); - //wp_die(esc_html__('Error while sending form (no key). Please try again.', 'enable-media-replace')); } $post_id = isset($_POST['ID']) ? intval($_POST['ID']) : null; // sanitize, post_id. if (is_null($post_id)) { $this->viewError(self::ERROR_FORM); -// wp_die(esc_html__('Error in request. Please try again', 'enable-media-replace')); } + $attachment = get_post($post_id); + + if (! \emr()->checkImagePermission($attachment)) + { + $this->viewError(self::ERROR_IMAGE_PERMISSION); + wp_die( esc_html__('You do not have permission to upload files for this author.', 'enable-media-replace') ); + } + $this->setView($post_id); $result = $this->replaceBackground($post_id, $key); if (false === $result->success) { $this->view->errorMessage = $result->message; - $this->viewError(); + $this->viewError(self::ERROR_DOWNLOAD_FAILED); } elseif (! file_exists($result->image)) { $this->viewError(self::ERROR_DOWNLOAD_FAILED); } -// $result = $replacer->replaceWith($result->image, $source->getFileName() , true); -//$params = array(); $replaceController = new ReplaceController($post_id); $sourceFile = $replaceController->getSourceFile();
Exploit Outline
The exploit relies on the fact that the background replacement endpoint only checks for the `upload_files` capability (Author+) but not if the user can edit the specific attachment ID. 1. Log in as a user with the Author role. 2. Create a dummy attachment (Author-owned) and initiate the "Remove Background" process to generate a valid `media_remove_background` nonce and a ShortPixel API `key` representing the processed image. 3. Identify the target `attachment_id` (e.g., a file owned by the Admin). 4. Send a POST request to the plugin's background removal handler (`wp-admin/upload.php?page=enable-media-replace/enable-media-replace.php`) with the following parameters: - `ID`: The target attachment ID (Admin's image). - `key`: The captured API key from the Author's processed image. - `emr_nonce`: The captured valid nonce. - `action`: `emr_remove_background` (or equivalent routing parameter). 5. The plugin will download the background-removed image and overwrite the target Admin file because it fails to call `current_user_can('edit_post', $post_id)`.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.