CVE-2026-4068

Add Custom Fields to Media <= 2.0.3 - Cross-Site Request Forgery to Custom Field Deletion via 'delete' Parameter

mediumCross-Site Request Forgery (CSRF)
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
2.0.4
Patched in
1d
Time to patch

Description

The Add Custom Fields to Media plugin for WordPress is vulnerable to Cross-Site Request Forgery in all versions up to, and including, 2.0.3. This is due to missing nonce validation on the field deletion functionality in the admin display template. The plugin properly validates a nonce for the 'add field' operation (line 24-36), but the 'delete field' operation (lines 38-49) processes the $_GET['delete'] parameter and calls update_option() without any nonce verification. This makes it possible for unauthenticated attackers to delete arbitrary custom media fields via a forged request, granted they can trick a site administrator into performing an action such as clicking on a link.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
Required
Scope
Unchanged
None
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=2.0.3
PublishedMarch 18, 2026
Last updatedMarch 19, 2026

What Changed in the Fix

Changes introduced in v2.0.4

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-4068 - Add Custom Fields to Media CSRF ## 1. Vulnerability Summary The **Add Custom Fields to Media** plugin (<= 2.0.3) contains a Cross-Site Request Forgery (CSRF) vulnerability in its administrative settings page. Specifically, the functionality to delete cu…

Show full research plan

Exploitation Research Plan: CVE-2026-4068 - Add Custom Fields to Media CSRF

1. Vulnerability Summary

The Add Custom Fields to Media plugin (<= 2.0.3) contains a Cross-Site Request Forgery (CSRF) vulnerability in its administrative settings page. Specifically, the functionality to delete custom media fields does not perform any nonce validation or capability checks before modifying the site's configuration via update_option(). An attacker can trick a logged-in administrator into clicking a link that deletes defined custom fields, potentially disrupting site functionality or removing critical metadata.

2. Attack Vector Analysis

  • Vulnerable Endpoint: /wp-admin/options-general.php?page=add-custom-fields-to-media
  • Hook: Registered via admin_menu in the Add_Custom_Fields_To_Media_Admin class (referenced in includes/class-add-custom-fields-to-media.php).
  • HTTP Method: GET
  • Vulnerable Parameter: delete
  • Authentication Requirement: Must be an authenticated user with manage_options capability (typically an Administrator).
  • Preconditions: At least one custom field must already exist in the thisismyurl_custom_media_fields option.

3. Code Flow

  1. The administrator visits the plugin's settings page: wp-admin/options-general.php?page=add-custom-fields-to-media.
  2. The file admin/partials/add-custom-fields-to-media-admin-display.php is loaded to render the UI.
  3. On Line 22, a nonce is verified for the addition of fields, but it is stored in the $nonce variable:
    $nonce = isset( $_REQUEST['_wpnonce'] ) ? wp_verify_nonce( sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) ), 'media_custom_fields_nonce' ) : false;
    
  4. On Line 38, the "Delete field" logic begins. It checks if $_GET['delete'] is present and if $media_custom_fields is an array:
    if ( ( isset( $_GET['delete'] ) && ! empty( $_GET['delete'] ) ) && is_array( $media_custom_fields ) ) {
    
  5. Critically, this block (Lines 38-49) does not check the $nonce variable or call check_admin_referer().
  6. The code iterates through existing fields, filters out the one matching $_GET['delete'], and updates the WordPress option:
    update_option( 'thisismyurl_custom_media_fields', $new_custom_fields );
    

4. Nonce Acquisition Strategy

No nonce is required for this specific exploit.
The vulnerability exists precisely because the delete operation fails to verify the nonce (media_custom_fields_nonce) that is otherwise used for the "Add Field" operation on the same page. The execution agent can proceed directly to the forgery.

5. Exploitation Strategy

Step 1: Discover Target unique_id

The attacker needs to know the unique_id of the field they wish to delete. Since this is a CSRF attack, they can guess common IDs (like copyright or author) or target a specific ID if known.

Step 2: Forged Request

The exploit is a simple GET request. In a real-world scenario, this would be an <img> tag or an auto-submitting form on an attacker-controlled site. For the PoC agent, a direct authenticated http_request to the target URL is sufficient.

  • Request Type: GET
  • URL: http://[TARGET]/wp-admin/options-general.php?page=add-custom-fields-to-media&delete=[FIELD_ID]
  • Headers: Authentication cookies for an Administrator must be included.

6. Test Data Setup

Before exploitation, we must ensure a custom field exists to be deleted.

  1. Create a field via WP-CLI:
    wp option update thisismyurl_custom_media_fields '[{"unique_id":"poc_field_to_delete","name":"PoC Field","help":"This will be deleted"}]' --format=json
    
  2. Verify the field exists:
    wp option get thisismyurl_custom_media_fields
    

7. Expected Results

  • The HTTP request to the delete endpoint should return a 200 OK status (as it loads the admin page).
  • The internal state of the WordPress database will change: the entry with unique_id "poc_field_to_delete" will be removed from the thisismyurl_custom_media_fields array.

8. Verification Steps

After performing the exploit with the http_request tool, run the following command to confirm the deletion:

# Check the option value. It should be an empty array or missing the poc_field_to_delete entry.
wp option get thisismyurl_custom_media_fields --format=json

If the exploit is successful, the output will NOT contain "poc_field_to_delete".

9. Alternative Approaches

If the plugin logic requires the page to be re-rendered to confirm the deletion, we can navigate to the page using browser_navigate as an admin and then check the HTML for the absence of the field table:

// Using browser_eval to check if the field table contains our ID
const tableContent = document.body.innerHTML;
const isDeleted = !tableContent.includes('poc_field_to_delete');
console.log(isDeleted);

However, since update_option() is called during the initial execution of the script when the delete parameter is present, a single GET request is technically sufficient to trigger the vulnerability.

Research Findings
Static analysis — not yet PoC-verified

Summary

The Add Custom Fields to Media plugin for WordPress is vulnerable to Cross-Site Request Forgery (CSRF) due to a lack of nonce validation on its field deletion functionality. An attacker can exploit this by tricking a site administrator into clicking a crafted link, which results in the deletion of custom media fields via the 'delete' GET parameter.

Vulnerable Code

// admin/partials/add-custom-fields-to-media-admin-display.php line 22
$nonce = isset( $_REQUEST['_wpnonce'] ) ? wp_verify_nonce( sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) ), 'media_custom_fields_nonce' ) : false;

if ( $nonce && isset( $_POST['unique_id'] ) && ! empty( $_POST['unique_id'] ) && isset( $_POST['field_title'] ) && ! empty( $_POST['field_title'] ) && isset( $_POST['field_help'] ) ) {
	// Addition logic is protected by $nonce check above
    // ... (truncated)
}

// admin/partials/add-custom-fields-to-media-admin-display.php line 38
if ( ( isset( $_GET['delete'] ) && ! empty( $_GET['delete'] ) ) && is_array( $media_custom_fields ) ) {
	foreach ( $media_custom_fields as $check_for_delete ) {
		if ( urldecode( sanitize_text_field( wp_unslash( $_GET['delete'] ) ) ) !== $check_for_delete['unique_id'] ) {
			$new_custom_fields[] = array(
				'unique_id' => esc_attr( $check_for_delete['unique_id'] ),
				'name'      => esc_attr( $check_for_delete['name'] ),
				'help'      => esc_attr( $check_for_delete['help'] ),
			);
		}
	}
	update_option( 'thisismyurl_custom_media_fields', $new_custom_fields );
}

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/add-custom-fields-to-media/2.0.3/admin/partials/add-custom-fields-to-media-admin-display.php /home/deploy/wp-safety.org/data/plugin-versions/add-custom-fields-to-media/2.0.4/admin/partials/add-custom-fields-to-media-admin-display.php
--- /home/deploy/wp-safety.org/data/plugin-versions/add-custom-fields-to-media/2.0.3/admin/partials/add-custom-fields-to-media-admin-display.php	2025-06-29 19:26:34.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/add-custom-fields-to-media/2.0.4/admin/partials/add-custom-fields-to-media-admin-display.php	2026-03-13 00:20:38.000000000 +0000
@@ -36,6 +36,10 @@
 }
 
 if ( ( isset( $_GET['delete'] ) && ! empty( $_GET['delete'] ) ) && is_array( $media_custom_fields ) ) {
+	if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ), 'acfm_delete_field' ) ) {
+		wp_die( esc_html__( 'Security check failed.', 'add-custom-fields-to-media' ) );
+	}
+	$new_custom_fields = array();
 	foreach ( $media_custom_fields as $check_for_delete ) {
 		if ( urldecode( sanitize_text_field( wp_unslash( $_GET['delete'] ) ) ) !== $check_for_delete['unique_id'] ) {
 			$new_custom_fields[] = array(
@@ -69,7 +73,7 @@
 						<td><?php echo esc_html( $custom_field['unique_id'] ); ?></td>
 						<td><?php echo esc_html( $custom_field['name'] ); ?></td>
 						<td><?php echo esc_html( $custom_field['help'] ); ?></td>
-						<td><a href="<?php echo esc_url( 'options-general.php?page=add-custom-fields-to-media&delete=' . $custom_field['unique_id'] ); ?>" style="text-decoration: none;" title="Delete Field"><span class="dashicons dashicons-trash"></span></a></td>
+						<td><a href="<?php echo esc_url( wp_nonce_url( 'options-general.php?page=add-custom-fields-to-media&delete=' . $custom_field['unique_id'], 'acfm_delete_field' ) ); ?>" style="text-decoration: none;" title="Delete Field"><span class="dashicons dashicons-trash"></span></a></td>
 					</tr>
 				<?php } ?>
 			</tbody>

Exploit Outline

The exploit targets the plugin's settings page at `/wp-admin/options-general.php?page=add-custom-fields-to-media`. An attacker identifies or guesses a custom field's 'unique_id' and crafts a GET request containing the 'delete' parameter set to that ID. Because the plugin logic in versions up to 2.0.3 fails to verify a nonce before calling update_option() to remove the field from the database, an authenticated administrator can be tricked (via social engineering or a malicious site) into triggering the deletion simply by visiting the crafted URL.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.