Zarinpal Gateway for WooCommerce <= 5.0.16 - Improper Access Control to Payment Status Update
Description
The Zarinpal Gateway for WooCommerce plugin for WordPress is vulnerable to Improper Access Control to Payment Status Update in all versions up to and including 5.0.16. This is due to the payment callback handler 'Return_from_ZarinPal_Gateway' failing to validate that the authority token provided in the callback URL belongs to the specific order being marked as paid. This makes it possible for unauthenticated attackers to potentially mark orders as paid without proper payment by reusing a valid authority token from a different transaction of the same amount.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:HTechnical Details
<=5.0.16What Changed in the Fix
Changes introduced in v5.0.17
Source Code
WordPress.org SVN# Research Plan: Zarinpal Gateway Payment Status Bypass (CVE-2026-2592) ## 1. Vulnerability Summary The **Zarinpal Gateway for WooCommerce** plugin (up to 5.0.16) contains an improper access control vulnerability in its payment callback handler. The function `Return_from_ZarinPal_Gateway` processes…
Show full research plan
Research Plan: Zarinpal Gateway Payment Status Bypass (CVE-2026-2592)
1. Vulnerability Summary
The Zarinpal Gateway for WooCommerce plugin (up to 5.0.16) contains an improper access control vulnerability in its payment callback handler. The function Return_from_ZarinPal_Gateway processes payment confirmations from Zarinpal. While it verifies with the Zarinpal API that a given Authority token is paid, it fails to ensure that the Authority token being verified is the one actually associated with the order_id provided in the request.
This allows an attacker to take a valid Authority token from a previously completed, low-value transaction and reuse it to mark a new, unpaid order of the same amount as "Paid" by passing the new order's ID in the callback URL.
2. Attack Vector Analysis
- Endpoint:
/?wc-api=wc_zpal - Hook:
add_action('woocommerce_api_wc_zpal', array($this, 'Return_from_ZarinPal_Gateway')) - Vulnerable Parameter:
Authorityandorder_id(via$_GET) - Authentication: Unauthenticated
- Preconditions:
- The attacker must have a valid
Authoritytoken that has been marked as paid/verified by Zarinpal (can be obtained by completing a legitimate small transaction). - The target order must have the same total amount as the transaction associated with the reused
Authoritytoken (since the Zarinpal Verification API requires the amount).
- The attacker must have a valid
3. Code Flow
- Entry Point: An unauthenticated HTTP GET request is made to
/?wc-api=wc_zpal. - Handler: WooCommerce triggers
WC_ZPal::Return_from_ZarinPal_Gateway. - Order Retrieval: The plugin retrieves the order ID from
$_GET['order_id']and the authority from$_GET['Authority']. - Verification: The plugin calls
ZarinpalHelperClass::verify, passing theAuthorityand the current order's total amount. - The Flaw: The Zarinpal API confirms that the
Authorityis valid and paid for that amount. The plugin then calls$order->payment_complete()or updates the status. It never checks if$order->get_meta('_zarinpal_authority')matches theAuthorityprovided in$_GET. - Sink:
$order->payment_complete()is called, marking the order as paid.
4. Nonce Acquisition Strategy
This vulnerability exists in a webhook/callback handler registered via the woocommerce_api_{id} hook.
- Nonce Requirement: None. These endpoints are designed to be accessed by external payment gateways and do not implement WordPress nonces.
- Verification: The
Return_from_ZarinPal_Gatewayfunction does not contain anycheck_ajax_refererorwp_verify_noncecalls.
5. Exploitation Strategy
The exploit involves a "Replay Attack" of a valid authority token against a different order ID.
Step 1: Setup Zarinpal in Sandbox Mode
- Configure the gateway settings via WP-CLI to enable it and set it to sandbox mode.
- Set a dummy Merchant Code.
Step 2: Obtain a Valid "Paid" Authority
- Create a "Legitimate Order" (Order A) for a specific amount (e.g., 1000).
- Initiate payment to get an
Authoritytoken. - In Sandbox mode, the token can usually be verified as paid by calling the callback with
Status=OK.
Step 3: Create the Target Order
- Create a "Target Order" (Order B) as a guest user for the same amount (1000).
- Capture the
order_idfor Order B.
Step 4: Perform the Bypass
- Send an unauthenticated request to the callback URL targeting Order B but using the
Authorityfrom Order A.
Request:
GET /?wc-api=wc_zpal&order_id=[ORDER_B_ID]&Authority=[ORDER_A_AUTHORITY]&Status=OK HTTP/1.1
Host: [target_host]
6. Test Data Setup
- Product: Create a product with price 1000:
wp post create --post_type=product --post_title='Test Product' --post_status=publish --meta_input='{"_price":1000, "_regular_price":1000}' - Plugin Config:
wp option update woocommerce_wc_zpal_settings '{"enabled":"yes","merchantcode":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","sandbox":"yes"}' --format=json - Order A (Source): Create an order, get its ID.
- Order B (Target): Create another order, get its ID.
7. Expected Results
- The HTTP response should indicate a successful redirection or a success message (depending on
successMessagesetting). - The "Target Order" (Order B) status should change from
pendingtoprocessingorcompletedin the WooCommerce database.
8. Verification Steps
- Check Order Status:
wp wc order get [ORDER_B_ID] --fields=status
Expected: "processing" or "completed" - Check Order Meta:
wp post meta get [ORDER_B_ID] _zarinpal_authority
Note: This value will be the ORIGINAL authority for Order B, proving that we successfully used Order A's authority to pay for Order B despite them not matching.
9. Alternative Approaches
If the plugin performs an internal lookup of the order by authority before checking the order_id parameter:
- Identify if the plugin uses
get_postswithmeta_queryfor_zarinpal_authority. - If it does, the exploit may require finding a logic branch where the
order_idparameter takes precedence over the meta lookup. - In version 5.0.16, the vulnerability specifically stems from trusting the
order_idin the URL and verifying theAuthorityin the URL independently.
Summary
The Zarinpal Gateway for WooCommerce plugin fails to validate that the authority token provided during a payment callback belongs to the order being processed. This allows unauthenticated attackers to mark orders as paid by reusing a valid authority token from a previous transaction of the same amount.
Vulnerable Code
// class-wc-gateway-zarinpal.php around line 386 if (isset($_GET['Status']) && $_GET['Status'] === 'OK') { $authority = sanitize_text_field($_GET['Authority']); $order_total = $order->get_total(); $amount = intval($order_total); $currency = $order->get_currency(); // ... logic follows to verify the token with Zarinpal API and call $order->payment_complete()
Security Fix
@@ -356,7 +356,15 @@ $cart_json, $referrer_id ); + $order->update_meta_data('_zarinpal_authority', $authority); + $authority_history = $order->get_meta('_zarinpal_authority_history'); + if (!is_array($authority_history)) { + $authority_history = array(); + } + $authority_history[] = $authority; + $order->update_meta_data('_zarinpal_authority_history', $authority_history); + $order->save(); $note = sprintf(__('کاربر به درگاه پرداخت هدایت شد. شناسه تراکنش: %s', WC_ZPAL_TEXT_DOMAIN), $authority); $order->add_order_note($note); @@ -376,9 +384,33 @@ wp_redirect(wc_get_checkout_url()); exit; } + + if ($order->is_paid()) { + wp_redirect($this->get_return_url($order)); + exit; + } + if (isset($_GET['Status']) && $_GET['Status'] === 'OK') { $authority = sanitize_text_field($_GET['Authority']); - + $stored_authority = $order->get_meta('_zarinpal_authority'); + $authority_history = $order->get_meta('_zarinpal_authority_history'); + $is_valid_authority = false; + + if (!empty($stored_authority) && $authority === $stored_authority) { + $is_valid_authority = true; + } + + if (!$is_valid_authority && is_array($authority_history) && in_array($authority, $authority_history, true)) { + $is_valid_authority = true; + } + + if (!$is_valid_authority) { + $order->add_order_note(__('تلاش برای پرداخت با توکن نامعتبر. توکن ارسالی با توکن سفارش مطابقت ندارد.', WC_ZPAL_TEXT_DOMAIN)); + wc_add_notice(__('توکن پرداخت نامعتبر است. لطفاً مجدداً تلاش کنید.', WC_ZPAL_TEXT_DOMAIN), 'error'); + wp_redirect(wc_get_checkout_url()); + exit; + } + $order_total = $order->get_total(); $amount = intval($order_total); $currency = $order->get_currency();
Exploit Outline
The exploit is an unauthenticated replay attack against the payment callback endpoint. 1. Target Endpoint: `/?wc-api=wc_zpal`. 2. Methodology: An attacker first places a legitimate, low-value order and completes the payment to obtain a valid `Authority` token that is marked as "Paid" in the Zarinpal system. 3. The attacker then creates a second target order for the same total amount (e.g., purchasing high-value items totaling the same price as the first order). 4. The attacker sends a GET request to the callback endpoint with the target order's ID (`order_id`) and the valid `Authority` token from the first order. 5. Because the plugin only verifies that the `Authority` token is valid and paid for the given amount (calling `ZarinpalHelperClass::verify`), and fails to check if that token is the one specifically assigned to the provided `order_id`, it marks the target order as paid.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.