User Registration & Membership <= 4.4.8 - Cross-Site Request Forgery to Arbitrary Post Deletion
Description
The User Registration & Membership – Custom Registration Form Builder, Custom Login Form, User Profile, Content Restriction & Membership Plugin plugin for WordPress is vulnerable to Cross-Site Request Forgery in all versions up to, and including, 4.4.8. This is due to missing or incorrect nonce validation on the 'process_row_actions' function with the 'delete' action. This makes it possible for unauthenticated attackers to delete arbitrary post via a forged request granted they can trick a site administrator into performing an action such as clicking on a link.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:LTechnical Details
<=4.4.8What Changed in the Fix
Changes introduced in v4.4.9
Source Code
WordPress.org SVN# Exploitation Research Plan - CVE-2025-14976 ## 1. Vulnerability Summary The **User Registration & Membership** plugin (versions <= 4.4.8) is vulnerable to **Cross-Site Request Forgery (CSRF)** due to missing nonce validation in the `process_row_actions` function. This function handles row-level a…
Show full research plan
Exploitation Research Plan - CVE-2025-14976
1. Vulnerability Summary
The User Registration & Membership plugin (versions <= 4.4.8) is vulnerable to Cross-Site Request Forgery (CSRF) due to missing nonce validation in the process_row_actions function. This function handles row-level actions for administrative list tables (such as the forms list). Specifically, the delete action does not verify a security nonce before calling wp_delete_post(). An attacker can trick an authenticated administrator into visiting a crafted URL, resulting in the permanent deletion of arbitrary WordPress posts or pages.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin.php - Vulnerable Action:
delete - Query Parameters:
page: The slug of the plugin page (typicallyuser-registration).action: Set todelete.id: The ID of the post/form to be deleted.
- Authentication: Required (Administrator).
- Preconditions: An administrator must be logged in and tricked into clicking a link or visiting a page that triggers the GET/POST request.
3. Code Flow
- The plugin registers an admin menu page with the slug
user-registration(seeincludes/admin/class-ur-admin-menus.php). - The page callback initializes a class extending
UR_List_Table(defined inincludes/abstracts/abstract-ur-list-table.php). - The list table class (e.g.,
UR_Admin_Forms_Table) callsprocess_row_actions()during its lifecycle (usually withinprepare_items()or via anadmin_inithook). process_row_actions()(inferred) checks$_REQUEST['action'].- If
action === 'delete', it retrieves$_REQUEST['id']. - The code calls
wp_delete_post( $id, true )without callingcheck_admin_referer()orwp_verify_nonce(). - The post is permanently deleted from the database.
4. Nonce Acquisition Strategy
No nonce is required.
The essence of this vulnerability (CVE-2025-14976) is the complete absence of nonce validation in the process_row_actions function for the delete action. Therefore, an attacker does not need to extract a nonce to perform the exploit.
5. Exploitation Strategy
The exploit will be demonstrated by deleting a dummy post using the administrator's session.
Step-by-Step Plan:
- Preparation: Create a target post and note its ID.
- Trigger Request: Use the
http_requesttool to simulate an administrator clicking a malicious link. - Request Details:
- Method:
GET(orPOST, butGETis often sufficient forWP_List_Tableactions if not restricted). - URL:
http://localhost:8080/wp-admin/admin.php?page=user-registration&action=delete&id=[TARGET_ID] - Headers: Must include valid administrator cookies.
- Method:
- Verification: Check if the post still exists using WP-CLI.
6. Test Data Setup
- Target Post: Create a standard WordPress post to be deleted.
wp post create --post_type=post --post_title="Sensitive Data" --post_status=publish # Note the returned ID (e.g., 123) - Plugin Form (Optional): Create a plugin form to see if the action is intended for forms but works on any post.
wp post create --post_type=user_registration --post_title="Registration Form 1" --post_status=publish # Note the returned ID (e.g., 124)
7. Expected Results
- The HTTP request should return a
302 Redirect(back to the list table) or a200 OK. - The post with the specified
idshould be permanently removed from thewp_poststable. - The
wp post get [ID]command should return an error indicating the post does not exist.
8. Verification Steps
After executing the http_request, run the following to confirm deletion:
# Check the specific post ID
wp post get [TARGET_ID] --field=ID
# If the command returns "Error: Could not find the post with ID [TARGET_ID]", the exploit was successful.
9. Alternative Approaches
If the GET request fails because the plugin enforces POST (unlikely for row actions in this version), use a POST request:
- Method:
POST - URL:
http://localhost:8080/wp-admin/admin.php?page=user-registration - Body (URL-encoded):
action=delete&id=[TARGET_ID] - Content-Type:
application/x-www-form-urlencoded
If the page slug is different (e.g., if targeting a different list within the plugin), try:
page=user-registration-registrationspage=ur-settings(if row actions exist there)
The most likely target is the primary forms list at page=user-registration.
Summary
The User Registration & Membership plugin for WordPress is vulnerable to Cross-Site Request Forgery (CSRF) due to missing nonce validation in the `process_row_actions` function of its abstract list table. This allows unauthenticated attackers to permanently delete or trash arbitrary posts or forms by tricking a logged-in administrator into clicking a specially crafted link.
Vulnerable Code
/* includes/abstracts/abstract-ur-list-table.php */ case 'bulk_trash': case 'trash': if ( ! current_user_can( 'delete_posts' ) ) { wp_die( esc_html__( 'You do not have permission to trash posts!', 'user-registration' ) ); } else { $post_ids = isset( $_REQUEST[ $this->_args['singular'] ] ) ? array_map( 'absint', (array) $_REQUEST[ $this->_args['singular'] ] ) : ''; $this->bulk_trash( $post_ids ); } break; --- case 'bulk_delete': case 'delete': if ( ! current_user_can( 'delete_posts' ) ) { wp_die( esc_html__( 'You do not have permission to delete posts!', 'user-registration' ) ); } else {
Security Fix
@@ -263,26 +263,34 @@ case 'bulk_trash': case 'trash': + check_admin_referer( 'bulk-' . $this->_args['plural'] ); + if ( ! current_user_can( 'delete_posts' ) ) { wp_die( esc_html__( 'You do not have permission to trash posts!', 'user-registration' ) ); } else { $post_ids = isset( $_REQUEST[ $this->_args['singular'] ] ) ? array_map( 'absint', (array) $_REQUEST[ $this->_args['singular'] ] ) : ''; $this->bulk_trash( $post_ids ); } + break; case 'bulk_untrash': case 'untrash': + check_admin_referer( 'bulk-' . $this->_args['plural'] ); + if ( ! current_user_can( 'edit_posts' ) ) { wp_die( esc_html__( 'You do not have permission to untrash posts!', 'user-registration' ) ); } else { $post_ids = isset( $_REQUEST[ $this->_args['singular'] ] ) ? array_map( 'absint', (array) $_REQUEST[ $this->_args['singular'] ] ) : ''; $this->bulk_untrash( $post_ids ); } + break; case 'bulk_delete': case 'delete': + check_admin_referer( 'bulk-' . $this->_args['plural'] ); + if ( ! current_user_can( 'delete_posts' ) ) { wp_die( esc_html__( 'You do not have permission to delete posts!', 'user-registration' ) ); } else {
Exploit Outline
1. Identify the post ID of a target form or page managed by the plugin. 2. Construct a malicious URL targeting the plugin's administration table row actions: `/wp-admin/admin.php?page=user-registration&action=delete&id=[TARGET_ID]`. 3. Entice an authenticated WordPress administrator to click this link or visit a page that triggers this request (e.g., via a hidden iframe or image tag). 4. The plugin's `UR_List_Table::process_row_actions` method processes the `delete` or `trash` action without verifying a security nonce. 5. The target post is permanently deleted or moved to the trash using the administrator's privileges.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.