CVE-2025-14444

RegistrationMagic – Custom Registration Forms, User Registration, Payment, and User Login <= 6.0.6.9 - Unauthenticated Payment Bypass via rm_process_paypal_sdk_payment

mediumInsufficient Verification of Data Authenticity
5.3
CVSS Score
5.3
CVSS Score
medium
Severity
6.0.7.0
Patched in
1d
Time to patch

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:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
None
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=6.0.6.9
PublishedFebruary 17, 2026
Last updatedFebruary 18, 2026

Source Code

WordPress.org SVN
Research Plan
Unverified

# 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

  1. Hook Registration: The plugin registers the AJAX actions in includes/class_rm_ajax.php or the main controller:
    add_action('wp_ajax_nopriv_rm_process_paypal_sdk_payment', array($this, 'rm_process_paypal_sdk_payment'));
  2. Entry Point: The request hits RM_Public_Controller::rm_process_paypal_sdk_payment() (or a similar method in RM_Public).
  3. Data Extraction: The function extracts submission_id and order_id from $_POST.
  4. Vulnerable Logic:
    • It likely verifies a nonce (see Section 4).
    • It checks if the status or similar parameter provided by the client equals COMPLETED.
    • Without calling https://api-m.paypal.com/v2/checkout/orders/{order_id}, it proceeds to call RM_Services::update_submission_payment_status() or RM_Submission::update_status().
  5. 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.

  1. Shortcode Identification: Identify the shortcode used to display RM forms: [RM_Form id='X'].
  2. Page Creation: Create a public page containing an RM form that requires payment.
    wp post create --post_type=page --post_status=publish --post_title="Payment Form" --post_content='[RM_Form id="1"]'
    
    (Note: Ensure Form ID 1 exists and is a paid form.)
  3. Nonce Extraction:
    • Navigate to the newly created page using browser_navigate.
    • RegistrationMagic localizes its AJAX variables into the global rm_ajax_vars or rm_front_vars object.
    • Use browser_eval to extract the nonce:
      // Expected variable name based on RM source patterns
      window.rm_ajax_vars?.nonce || window.rm_front_vars?.nonce
      
  4. 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

  1. Create a Form: Use WP-CLI or the UI to create a RegistrationMagic form (ID 1).
  2. Add a Product/Price: Configure a "Fixed Price" for the form (e.g., $10).
  3. Configure PayPal: Enable the PayPal SDK payment gateway in RM Global Settings (even with sandbox/fake credentials).
  4. Public Page: Place the form on a page: [RM_Form id='1'].
  5. 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_id moves 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

  1. Check Submission Status:
    wp db query "SELECT * FROM wp_rm_submissions WHERE submission_id = [ID]"
    
    Verify the status column is no longer 'pending'.
  2. Check Payment Records:
    wp db query "SELECT * FROM wp_rm_payments WHERE submission_id = [ID]"
    
    Verify a payment record exists with status = 'completed'.
  3. Check User:
    Verify the user created by the submission is active and has the correct role.

9. Alternative Approaches

  • Parameter Variation: If status=COMPLETED fails, try payment_status=Completed or intent=CAPTURE.
  • Direct Submission Update: If the AJAX handler requires an existing order_id from the database, look for the endpoint where the order_id is first registered (likely rm_create_paypal_order) and call it before the process payment action.
  • Check for rm_admin_ajax: If rm_process_paypal_sdk_payment is not found, check for generic handlers like rm_admin_ajax or rm_front_ajax which might route requests to a payment service.
Research Findings
Static analysis — not yet PoC-verified

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

--- a/public/controllers/class_rm_public_controller.php
+++ b/public/controllers/class_rm_public_controller.php
@@ -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.