CVE-2025-14078

PAYGENT for WooCommerce <= 2.4.6 - Missing Authorization to Unauthenticated Payment Callback Manipulation

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

Description

The PAYGENT for WooCommerce plugin for WordPress is vulnerable to Missing Authorization in all versions up to, and including, 2.4.6. This is due to missing authorization checks on the paygent_check_webhook function combined with the paygent_permission_callback function unconditionally returning true on line 199. This makes it possible for unauthenticated attackers to manipulate payment callbacks and modify order statuses by sending forged payment notifications via the `/wp-json/paygent/v1/check/` endpoint.

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.4.6
PublishedJanuary 16, 2026
Last updatedJanuary 17, 2026

What Changed in the Fix

Changes introduced in v2.4.7

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan - CVE-2025-14078 ## 1. Vulnerability Summary The **PAYGENT for WooCommerce** plugin (versions <= 2.4.6) contains a missing authorization vulnerability in its REST API callback handler. The plugin registers a REST route `/wp-json/paygent/v1/check/` intended for Paygent p…

Show full research plan

Exploitation Research Plan - CVE-2025-14078

1. Vulnerability Summary

The PAYGENT for WooCommerce plugin (versions <= 2.4.6) contains a missing authorization vulnerability in its REST API callback handler. The plugin registers a REST route /wp-json/paygent/v1/check/ intended for Paygent payment notifications (webhooks). However, the paygent_permission_callback function, which is supposed to restrict access to authorized Paygent IP addresses, fails to properly enforce these restrictions (unconditionally returning true or failing to return false in a way that blocks access).

This allows an unauthenticated attacker to send forged payment notifications. The handler paygent_check_webhook processes these notifications without any cryptographic signature verification or shared secret validation, allowing an attacker to manipulate order statuses and inject order notes by providing an arbitrary trading_id (WooCommerce Order ID).

2. Attack Vector Analysis

  • Endpoint: POST /wp-json/paygent/v1/check/
  • Authentication: Unauthenticated (No WordPress login or Nonce required).
  • Payload Format: URL-encoded body (as processed by jp4wc_url_to_array).
  • Target Parameters:
    • trading_id: The numeric ID of the WooCommerce order to target.
    • payment_type: Determines which specific webhook logic is triggered (e.g., 02 for Credit Card, 26 for PayPay).
    • payment_status: The status code to report (used for order notes and status logic).

3. Code Flow

  1. Entry Point: includes/gateways/paygent/class-wc-paygent-endpoint.php
  2. Route Registration: paygent_register_routes() (Line 42) registers the /check route using WP_REST_Server::CREATABLE (POST).
  3. Authorization Check: paygent_permission_callback() (Line 199) is called. According to the vulnerability description, this function incorrectly returns true (or lacks a mandatory false return), allowing the request to proceed regardless of the requester's IP.
  4. Processing Logic: paygent_check_webhook($request) (Line 61) is executed:
    • It extracts the raw body via $request->get_body().
    • It converts the body string to an array using $jp4wc_framework->jp4wc_url_to_array($body_data).
    • It extracts trading_id, payment_status, and payment_type.
    • It sanitizes the trading_id to get the $order_id via preg_replace( '/[^0-9]/', '', $get_array['trading_id'] ).
    • It retrieves the order via wc_get_order( $order_id ).
  5. Sink: If the order exists, it adds an order note (Line 131) or calls specific gateway handlers like $this->paygent_cc_webhook( $order, $get_array ) (Line 104), which typically transitions the order to a "Processing" or "Completed" status.

4. Nonce Acquisition Strategy

This vulnerability resides in a REST API endpoint where the permission_callback is misconfigured.

  • Is a nonce needed? No. REST API endpoints typically use the _wpnonce parameter only when authenticated via session cookies. Since the permission_callback is vulnerable and intended for third-party webhooks, it does not expect or verify a WordPress nonce.
  • Bypass: The vulnerability itself is the bypass of the permission_callback.

5. Exploitation Strategy

The goal is to forge a payment success notification for an existing order.

  1. Step 1: Identify a Target Order:
    Obtain a valid WooCommerce Order ID. In a test environment, this can be done via WP-CLI.

  2. Step 2: Craft the Forged Webhook Payload:
    Construct a URL-encoded string containing Paygent-style parameters.

    • trading_id=[ORDER_ID]
    • payment_type=02 (Credit Card)
    • payment_status=20 (Typical "Success" or "Authorized" code in Paygent logic, though any value will trigger at least an order note).
  3. Step 3: Send the Request:
    Use the http_request tool to send a POST request to the target endpoint.

    Request Details:

    • Method: POST
    • URL: http://[TARGET_HOST]/wp-json/paygent/v1/check/
    • Headers: Content-Type: application/x-www-form-urlencoded
    • Body: trading_id=123&payment_type=02&payment_status=20
  4. Step 4: Analyze the Response:
    The endpoint responds with header( 'Content-type: text/plain; charset=utf-8' ); echo 'result=0';. A response of result=0 indicates the plugin processed the "notification".

6. Test Data Setup

  1. Ensure WooCommerce is installed and active.
  2. Ensure PAYGENT for WooCommerce (v2.4.6) is installed and active.
  3. Create a dummy product and place an order as a guest to generate an Order ID.
    • wp eval "wc_create_order(['customer_id' => 0]);" (or use the UI).
  4. Note the Order ID (e.g., 123).

7. Expected Results

  • The HTTP response body should be exactly result=0.
  • The targeted WooCommerce order should have a new note added by the system.
  • Depending on the payment_type handler (e.g., paygent_cc_webhook), the order status may change from pending to processing or on-hold.

8. Verification Steps

  1. Check Order Notes:
    Use WP-CLI to verify that the webhook injected a note.

    wp order note list [ORDER_ID]
    

    Look for: "I received a payment information inquiry telegram with a payment status of ..."

  2. Check Order Status:

    wp order get [ORDER_ID] --field=status
    

    Check if the status transitioned away from pending.

  3. Check Debug Logs (if enabled):
    The plugin calls $jp4wc_framework->jp4wc_debug_log. If debugging is active for the gateway, check wp-content/uploads/wc-logs/wc-paygent-....

9. Alternative Approaches

If the 02 (Credit Card) payment_type requires complex additional parameters (like specific Paygent transaction IDs), try other payment types:

  • payment_type=26 (PayPay)
  • payment_type=03 (Convenience Store)

Even if the internal webhook handlers (paygent_cc_webhook, etc.) fail due to missing secondary parameters, the primary paygent_check_webhook function will still execute:

$order->add_order_note( sprintf( __( 'I received a payment information inquiry telegram with a payment status of %s.', 'woocommerce-for-paygent-payment-main' ), $status_array[ $payment_status ] . ':' . $payment_status ) );

This confirms the unauthorized access and the ability to manipulate order metadata.

Research Findings
Static analysis — not yet PoC-verified

Summary

The PAYGENT for WooCommerce plugin is vulnerable to unauthorized payment callback manipulation due to flawed authorization checks in its REST API endpoint. Attackers can bypass IP-based restrictions by spoofing HTTP headers like X-Forwarded-For, allowing them to send forged payment notifications that modify WooCommerce order statuses and metadata.

Vulnerable Code

// includes/gateways/paygent/class-wc-paygent-endpoint.php

public function paygent_register_routes() {
    // POST /wp-json/paygent/v1/check/ .
    register_rest_route(
        'paygent/v1',
        '/check',
        array(
            'methods'             => WP_REST_Server::CREATABLE,
            'callback'            => array( $this, 'paygent_check_webhook' ),
            'permission_callback' => array( $this, 'paygent_permission_callback' ),
        )
    );
}

---

// includes/gateways/paygent/class-wc-paygent-endpoint.php:164

public function paygent_permission_callback( $request ) {
    $is_permitted_ips = apply_filters(
        'paygent_permitted_ips',
        array(
            '27.110.52.4', // Add Paygent IP address.
            '202.232.189.65', // SandBox IP address.
        )
    );

    // Get remote IP address from various sources.
    $remote_ip = '';

    // Check common headers for real IP.
    if ( ! empty( $_SERVER['HTTP_X_REAL_IP'] ) ) {
        $remote_ip = sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_REAL_IP'] ) );
    } elseif ( ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
        $forwarded_ips = explode( ',', sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) );
        $remote_ip     = trim( $forwarded_ips[0] );
    } elseif ( ! empty( $_SERVER['REMOTE_ADDR'] ) ) {
        $remote_ip = sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) );
    }

    $is_permitted = false;

    if ( ! empty( $remote_ip ) && in_array( $remote_ip, $is_permitted_ips, true ) ) {
        $is_permitted = true;
    }
    // ... (logic error follows where non-permitted requests are not explicitly returned false) ...

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/woocommerce-for-paygent-payment-main/2.4.6/includes/gateways/paygent/class-wc-paygent-endpoint.php /home/deploy/wp-safety.org/data/plugin-versions/woocommerce-for-paygent-payment-main/2.4.7/includes/gateways/paygent/class-wc-paygent-endpoint.php
--- /home/deploy/wp-safety.org/data/plugin-versions/woocommerce-for-paygent-payment-main/2.4.6/includes/gateways/paygent/class-wc-paygent-endpoint.php	2026-01-05 01:45:50.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/woocommerce-for-paygent-payment-main/2.4.7/includes/gateways/paygent/class-wc-paygent-endpoint.php	2026-01-06 03:08:42.000000000 +0000
@@ -161,14 +161,15 @@
 		// Get remote IP address from various sources.
 		$remote_ip = '';
 
-		// Check common headers for real IP.
-		if ( ! empty( $_SERVER['HTTP_X_REAL_IP'] ) ) {
+		// Check REMOTE_ADDR first as it's the most reliable and cannot be spoofed.
+		// Only use X-Real-IP and X-Forwarded-For as fallback methods.
+		if ( ! empty( $_SERVER['REMOTE_ADDR'] ) ) {
+			$remote_ip = sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) );
+		} elseif ( ! empty( $_SERVER['HTTP_X_REAL_IP'] ) ) {
 			$remote_ip = sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_REAL_IP'] ) );
 		} elseif ( ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
 			$forwarded_ips = explode( ',', sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) );
 			$remote_ip     = trim( $forwarded_ips[0] );
-		} elseif ( ! empty( $_SERVER['REMOTE_ADDR'] ) ) {
-			$remote_ip = sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) );
 		}
 
 		$is_permitted = false;

Exploit Outline

The exploit targets the Paygent REST API callback endpoint and requires no authentication. Methodology: 1. Identify a valid target WooCommerce Order ID. 2. Construct a POST request to `/wp-json/paygent/v1/check/`. 3. Add an `X-Forwarded-For` header containing a trusted Paygent IP address (e.g., `27.110.52.4`) to bypass the `permission_callback` check, which incorrectly prioritizes user-controlled headers for IP verification. 4. Provide a URL-encoded body payload containing `trading_id=[Order ID]`, `payment_type=02` (Credit Card), and a `payment_status` corresponding to a successful transaction (e.g., `20`). 5. Upon processing, the plugin will update the target order's notes and potentially transition its status to 'Processing' or 'Completed' without further verification of the payment's legitimacy.

Check if your site is affected.

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