Knit Pay – Cashfree, Instamojo, Razorpay, PayPal and more <= 9.4.0.0 - Missing Authorization
Description
The Knit Pay – Cashfree, Instamojo, Razorpay, PayPal and more plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in versions up to, and including, 9.4.0.0. This makes it possible for unauthenticated attackers to perform an unauthorized action.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:NTechnical Details
What Changed in the Fix
Changes introduced in v9.4.0.1
Source Code
WordPress.org SVN# CVE-2026-49070 Knit Pay – Missing Authorization Research Plan ## Vulnerability Summary The **Knit Pay** plugin (<= 9.4.0.0) is vulnerable to **Missing Authorization** in its PayPal gateway AJAX handlers. Specifically, the functions `ajax_create_order` and `ajax_capture_order` in `gateways/PayPal/…
Show full research plan
CVE-2026-49070 Knit Pay – Missing Authorization Research Plan
Vulnerability Summary
The Knit Pay plugin (<= 9.4.0.0) is vulnerable to Missing Authorization in its PayPal gateway AJAX handlers. Specifically, the functions ajax_create_order and ajax_capture_order in gateways/PayPal/Gateway.php perform security checks using WordPress nonces (check_ajax_referer) but fail to verify if the current user has the authority to modify the specific payment object identified by the payment_id parameter.
Because these handlers are likely registered as wp_ajax_nopriv_ actions (to support guest checkout), an unauthenticated attacker can obtain a valid nonce from any public payment page and then use it to manipulate the state or metadata (e.g., transaction_id) of any other pronamic_payment post in the system.
Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Actions:
knitpay_paypal_create_order(inferred fromajax_create_order)knitpay_paypal_capture_order(inferred fromajax_capture_order)
- Parameters:
action: The AJAX action name.nonce: A valid nonce for theknitpay_paypal_checkoutaction.payment_id: The integer ID of apronamic_paymentpost.order_id: (For capture) A PayPal order ID string.
- Preconditions:
- The PayPal gateway must be active.
- An attacker needs to find a valid
payment_id(enumerable via post IDs). - A valid nonce must be extracted from a page where the PayPal gateway is initialized.
Code Flow
- Entry Point: A POST request is sent to
admin-ajax.phpwith the actionknitpay_paypal_capture_order. - Nonce Check:
Gateway::ajax_capture_order()callscheck_ajax_referer( 'knitpay_paypal_checkout', 'nonce', false ). This checks if the nonce is valid for the current user (or guest session). - Object Retrieval: The code calls
$payment = \get_pronamic_payment( $payment_id ). - Vulnerable Sink: Without checking if the current user "owns" the payment session or has administrative privileges, the code proceeds to:
$payment->set_transaction_id( $order_id )$gateway->update_status( $payment )$payment->save()
- Impact: The
transaction_idmeta field of the payment post is updated in the database. If the providedorder_idcorresponds to a completed PayPal transaction (even from a different context),update_statusmay incorrectly mark the target payment as Success.
Nonce Acquisition Strategy
The nonce knitpay_paypal_checkout is generated for guest users (UID 0) and is valid for all guest sessions within the 12-24 hour window.
- Identify Script Localization: The plugin likely uses
wp_localize_scriptto pass the nonce to the frontend PayPal Smart Buttons. - Trigger Script Loading: The scripts are enqueued when a payment page for a PayPal-configured gateway is loaded.
- Extraction:
- Create a payment link using the Knit Pay admin interface (or CLI).
- Navigate to the payment link URL using
browser_navigate. - Use
browser_evalto extract the nonce:// Example target (based on common patterns in this plugin) window.knitpay_paypal_checkout_data?.nonce // OR look for localized data in the page source
- Action Matching: In
gateways/PayPal/Gateway.php, both handlers use the same action string:'knitpay_paypal_checkout'.
Exploitation Strategy
- Setup:
- Enable the PayPal gateway in Knit Pay.
- Create a "target" payment (e.g., via a WooCommerce checkout or a Payment Link). Note its
payment_id.
- Acquire Nonce:
- Create a second "dummy" payment.
- Visit the dummy payment page.
- Extract the
nonceforknitpay_paypal_checkout.
- Execute Attack:
- Send a POST request to
admin-ajax.php. action=knitpay_paypal_capture_ordernonce=[EXTRACTED_NONCE]payment_id=[TARGET_PAYMENT_ID]order_id=MALICIOUS_TRANSACTION_123
- Send a POST request to
- Verify: Check the metadata of the target payment post to confirm the
transaction_idwas changed.
Test Data Setup
- Gateway: Create a
pronamic_gatewaypost withpost_title="PayPal"and configure it for the PayPal integration. - Payment: Create a
pronamic_paymentpost representing a pending transaction. - Page: Create a WordPress page containing a payment button or link that uses the PayPal gateway to ensure the nonce is generated and localized.
Expected Results
- The AJAX request returns a
200 OK(or500if the PayPal API call fails, but only after thetransaction_idis set). - The target
pronamic_paymentpost will have its_pronamic_payment_transaction_id(or similar meta key) updated toMALICIOUS_TRANSACTION_123. - An unauthenticated user successfully modifies data belonging to a transaction they did not initiate.
Verification Steps
Use WP-CLI to inspect the payment meta before and after the attack:
# Get current transaction ID
wp post meta get [PAYMENT_ID] _pronamic_payment_transaction_id
# After exploit, check again
wp post meta get [PAYMENT_ID] _pronamic_payment_transaction_id
Alternative Approaches
If ajax_capture_order fails due to strict PayPal API validation, target ajax_create_order:
- Call
knitpay_paypal_create_orderwith a targetpayment_id. - This triggers
$gateway->api->create_order(...)and$payment->save(). - Successful execution will overwrite the
paypal_order_idmeta andtransaction_idof the target payment, which can be verified via WP-CLI.
Summary
The Knit Pay plugin for WordPress is vulnerable to missing authorization in its PayPal gateway AJAX handlers. Unauthenticated attackers can manipulate the transaction ID and status of arbitrary payment records by leveraging a publicly accessible nonce to call sensitive functions without ownership verification.
Vulnerable Code
// gateways/PayPal/Gateway.php:93 public static function ajax_create_order() { if ( ! check_ajax_referer( 'knitpay_paypal_checkout', 'nonce', false ) ) { wp_send_json_error( [ 'message' => 'Security check failed. Please refresh the page and try again.' ], 403 ); } $payment_id = isset( $_POST['payment_id'] ) ? absint( $_POST['payment_id'] ) : 0; // ... $payment = \get_pronamic_payment( $payment_id ); // ... if ( isset( $payment_order->id ) ) { $payment->set_meta( 'paypal_order_id', $payment_order->id ); $payment->set_transaction_id( $payment_order->id ); $payment->save(); --- // gateways/PayPal/Gateway.php:153 public static function ajax_capture_order() { if ( ! check_ajax_referer( 'knitpay_paypal_checkout', 'nonce', false ) ) { wp_send_json_error( [ 'message' => 'Security check failed. Please refresh the page and try again.' ], 403 ); } $order_id = isset( $_POST['order_id'] ) ? sanitize_text_field( wp_unslash( $_POST['order_id'] ) ) : ''; $payment_id = isset( $_POST['payment_id'] ) ? absint( $_POST['payment_id'] ) : 0; if ( empty( $order_id ) || empty( $payment_id ) ) { wp_send_json_error( [ 'message' => 'Invalid request parameters.' ], 400 ); } $payment = \get_pronamic_payment( $payment_id ); if ( null === $payment ) { wp_send_json_error( [ 'message' => 'Payment not found.' ], 404 ); } try { $integration = new Integration(); $gateway = $integration->get_gateway( $payment->get_config_id() ); $payment->set_transaction_id( $order_id ); $gateway->update_status( $payment ); $payment->save();
Security Fix
@@ -3719,16 +3719,16 @@ }, { "name": "composer/composer", - "version": "2.10.0", + "version": "2.10.1", "source": { "type": "git", "url": "https://github.com/composer/composer.git", - "reference": "c13824d95608b15913a7c0def0a3dea4474b71fc" + "reference": "4120703b9bda8795075047b40361d7ec4d2abe49" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/composer/zipball/c13824d95608b15913a7c0def0a3dea4474b71fc", - "reference": "c13824d95608b15913a7c0def0a3dea4474b71fc", + "url": "https://api.github.com/repos/composer/composer/zipball/4120703b9bda8795075047b40361d7ec4d2abe49", + "reference": "4120703b9bda8795075047b40361d7ec4d2abe49", "shasum": "" }, "require": { @@ -3816,7 +3816,7 @@ "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/composer/issues", "security": "https://github.com/composer/composer/security/policy", - "source": "https://github.com/composer/composer/tree/2.10.0" + "source": "https://github.com/composer/composer/tree/2.10.1" }, "funding": [ { @@ -3828,7 +3828,7 @@ "type": "github" } ], - "time": "2026-05-28T09:22:08+00:00" + "time": "2026-06-04T08:25:59+00:00" }, { "name": "composer/metadata-minifier", @@ -153,9 +153,8 @@ wp_send_json_error( [ 'message' => 'Security check failed. Please refresh the page and try again.' ], 403 ); } - $order_id = isset( $_POST['order_id'] ) ? sanitize_text_field( wp_unslash( $_POST['order_id'] ) ) : ''; $payment_id = isset( $_POST['payment_id'] ) ? absint( $_POST['payment_id'] ) : 0; - if ( empty( $order_id ) || empty( $payment_id ) ) { + if ( empty( $payment_id ) ) { wp_send_json_error( [ 'message' => 'Invalid request parameters.' ], 400 ); } @@ -164,11 +163,15 @@ wp_send_json_error( [ 'message' => 'Payment not found.' ], 404 ); } + $order_id = $payment->get_meta( 'paypal_order_id' ); + if ( empty( $order_id ) ) { + wp_send_json_error( [ 'message' => 'No PayPal order found for this payment.' ], 400 ); + } + try { $integration = new Integration(); $gateway = $integration->get_gateway( $payment->get_config_id() ); - $payment->set_transaction_id( $order_id ); $gateway->update_status( $payment ); $payment->save();
Exploit Outline
An unauthenticated attacker can manipulate payment records by first obtaining a valid 'knitpay_paypal_checkout' AJAX nonce from any public-facing page where the PayPal gateway is initialized. With this nonce, the attacker can send a POST request to admin-ajax.php with the action 'knitpay_paypal_capture_order'. By providing a target 'payment_id' (which can be enumerated) and a controlled 'order_id' string, the attacker forces the plugin to update the transaction ID and re-verify the payment status for that record. Since the function lacks ownership checks, this allows an attacker to modify metadata and potentially trigger status updates for payments they did not initiate.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.