CVE-2025-14976

User Registration & Membership <= 4.4.8 - Cross-Site Request Forgery to Arbitrary Post Deletion

mediumCross-Site Request Forgery (CSRF)
5.4
CVSS Score
5.4
CVSS Score
medium
Severity
4.4.9
Patched in
1d
Time to patch

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:L
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
Required
Scope
Unchanged
None
Confidentiality
Low
Integrity
Low
Availability

Technical Details

Affected versions<=4.4.8
PublishedJanuary 9, 2026
Last updatedJanuary 10, 2026
Affected pluginuser-registration

What Changed in the Fix

Changes introduced in v4.4.9

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# 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 (typically user-registration).
    • action: Set to delete.
    • 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

  1. The plugin registers an admin menu page with the slug user-registration (see includes/admin/class-ur-admin-menus.php).
  2. The page callback initializes a class extending UR_List_Table (defined in includes/abstracts/abstract-ur-list-table.php).
  3. The list table class (e.g., UR_Admin_Forms_Table) calls process_row_actions() during its lifecycle (usually within prepare_items() or via an admin_init hook).
  4. process_row_actions() (inferred) checks $_REQUEST['action'].
  5. If action === 'delete', it retrieves $_REQUEST['id'].
  6. The code calls wp_delete_post( $id, true ) without calling check_admin_referer() or wp_verify_nonce().
  7. 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:

  1. Preparation: Create a target post and note its ID.
  2. Trigger Request: Use the http_request tool to simulate an administrator clicking a malicious link.
  3. Request Details:
    • Method: GET (or POST, but GET is often sufficient for WP_List_Table actions 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.
  4. Verification: Check if the post still exists using WP-CLI.

6. Test Data Setup

  1. 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)
    
  2. 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 a 200 OK.
  • The post with the specified id should be permanently removed from the wp_posts table.
  • 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-registrations
  • page=ur-settings (if row actions exist there)

The most likely target is the primary forms list at page=user-registration.

Research Findings
Static analysis — not yet PoC-verified

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

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/user-registration/4.4.8/includes/abstracts/abstract-ur-list-table.php /home/deploy/wp-safety.org/data/plugin-versions/user-registration/4.4.9/includes/abstracts/abstract-ur-list-table.php
--- /home/deploy/wp-safety.org/data/plugin-versions/user-registration/4.4.8/includes/abstracts/abstract-ur-list-table.php	2024-05-31 06:24:32.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/user-registration/4.4.9/includes/abstracts/abstract-ur-list-table.php	2026-01-08 12:07:34.000000000 +0000
@@ -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.