CVE-2026-2732

Enable Media Replace <= 4.1.7 - Improper Authorization to Authenticated (Author+) Arbitrary Attachment Change via Background Replace

mediumMissing Authorization
5.4
CVSS Score
5.4
CVSS Score
medium
Severity
4.1.8
Patched in
1d
Time to patch

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:L
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
None
Confidentiality
Low
Integrity
Low
Availability

Technical Details

Affected versions<=4.1.7
PublishedMarch 3, 2026
Last updatedMarch 4, 2026
Affected pluginenable-media-replace

What Changed in the Fix

Changes introduced in v4.1.8

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# 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 by loadPost.
  • Vulnerable Params: ID (The attachment ID to be replaced) and key (The identifier for the processed image).
  • Authentication: Authenticated, Author-level role (capability upload_files).
  • Preconditions:
    1. A target attachment ID owned by an Administrator must exist.
    2. The attacker must have at least one attachment of their own to generate a valid key and nonce from the background removal flow.

3. Code Flow

  1. 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'));
    
  2. Routing: The route method determines which ViewController to load. For background removal, it invokes RemoveBackGroundViewController.
  3. Missing Permission Check (GET): In classes/ViewController/RemoveBackgroundViewController.php, the load() 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
        // ...
    }
    
  4. Processing (POST): When the "Replace" button is clicked after background removal, loadPost() is called:
    public 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
    }
    
    At no point in loadPost is there a check like current_user_can('edit_post', $post_id).

4. Nonce Acquisition Strategy

The nonce media_remove_background is required for the loadPost action.

  1. Target Identification: Find an attachment_id owned by the Admin.
  2. 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_eval to extract it:
      document.querySelector('input[name="emr_nonce"]').value
      
  3. Key Acquisition:
    • The key identifies 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 key is populated in the form. This key can then be repurposed against the Admin's ID.

5. Exploitation Strategy

  1. Login as a user with the Author role.
  2. Prepare Target: Identify an Admin-owned attachment (e.g., ID 10).
  3. Prepare Payload: Upload a simple image as Author (e.g., ID 11).
  4. Initiate Process: Start the "Remove Background" flow for ID 11.
  5. Intercept/Capture: Capture the emr_nonce and the key returned by the ShortPixel background removal service. (The key is typically returned via AJAX or updated in the UI after the image is processed).
  6. Execute Exploit: Submit a POST request with the captured key but pointing to the Admin's attachment ID.

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:
    emr_nonce=[CAPTURED_NONCE]&
    ID=10&
    key=[CAPTURED_KEY]&
    action=emr_remove_background
    
    (Note: The exact action parameter may vary depending on how the plugin's router handles the loadPost transition, but typically it follows the submenu page logic).

6. Test Data Setup

  1. Users:
    • admin_user (Administrator)
    • author_user (Author)
  2. Attachments:
    • admin_img.jpg uploaded by admin_user (Target ID).
    • author_img.jpg uploaded by author_user (Attacker-controlled source).

7. Expected Results

  • The plugin will successfully process the request because the Author has upload_files capability.
  • The ReplaceController will fetch the image associated with the key (which the Author generated) and overwrite the file associated with the Admin's attachment ID.
  • The Admin's image is now replaced by the Author's background-removed image.

8. Verification Steps

  1. Check Attachment Owner:
    wp post get [ADMIN_ID] --field=post_author (Should still be Admin).
  2. Check File Path:
    wp post get [ADMIN_ID] --field=guid
  3. 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.
  4. Check Meta:
    wp post meta get [ADMIN_ID] _emr_replace_author
    (In ReplaceController::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.

Research Findings
Static analysis — not yet PoC-verified

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

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/enable-media-replace/4.1.7/classes/ViewController/RemoveBackgroundViewController.php /home/deploy/wp-safety.org/data/plugin-versions/enable-media-replace/4.1.8/classes/ViewController/RemoveBackgroundViewController.php
--- /home/deploy/wp-safety.org/data/plugin-versions/enable-media-replace/4.1.7/classes/ViewController/RemoveBackgroundViewController.php	2023-09-14 11:51:00.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/enable-media-replace/4.1.8/classes/ViewController/RemoveBackgroundViewController.php	2026-03-03 10:21:48.000000000 +0000
@@ -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.