Commit ecdb1146e05 for woocommerce
commit ecdb1146e05bd8022b17d9f4fffa4c48d5b05bd4
Author: Darren Ethier <darren@roughsmootheng.in>
Date: Thu Sep 3 10:52:37 2026 -0400
Reject array order keys on pay, cancel and PayPal return paths (#68307)
An array-valued order key reached hash_equals() on the order pay,
cancel and PayPal return paths and raised an uncaught TypeError on
PHP 8, turning a bad link into a fatal error. Treat a non-string key
as absent so those paths fall through to their existing invalid-order
handling. Follows the same guard added for the order received page.
diff --git a/plugins/woocommerce/changelog/fix-wooplug-7659-order-key-array-guards b/plugins/woocommerce/changelog/fix-wooplug-7659-order-key-array-guards
new file mode 100644
index 00000000000..964c0ecbcb8
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-wooplug-7659-order-key-array-guards
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Treat array-valued order keys as invalid on the order pay, cancel and PayPal return paths instead of raising a PHP error.
diff --git a/plugins/woocommerce/includes/class-wc-form-handler.php b/plugins/woocommerce/includes/class-wc-form-handler.php
index 1e964a9bdf9..fb942e5200f 100644
--- a/plugins/woocommerce/includes/class-wc-form-handler.php
+++ b/plugins/woocommerce/includes/class-wc-form-handler.php
@@ -497,7 +497,7 @@ class WC_Form_Handler {
ob_start();
// Pay for existing order.
- $order_key = wp_unslash( $_GET['key'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
+ $order_key = is_string( $_GET['key'] ) ? wp_unslash( $_GET['key'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$order_id = absint( $wp->query_vars['order-pay'] );
$order = wc_get_order( $order_id );
@@ -866,7 +866,7 @@ class WC_Form_Handler {
) {
wc_nocache_headers();
- $order_key = wp_unslash( $_GET['order'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
+ $order_key = is_string( $_GET['order'] ) ? wp_unslash( $_GET['order'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$order_id = absint( $_GET['order_id'] );
$order = wc_get_order( $order_id );
/**
diff --git a/plugins/woocommerce/includes/gateways/paypal/includes/class-wc-gateway-paypal-response.php b/plugins/woocommerce/includes/gateways/paypal/includes/class-wc-gateway-paypal-response.php
index 46ef28752da..b8d251ec3c7 100644
--- a/plugins/woocommerce/includes/gateways/paypal/includes/class-wc-gateway-paypal-response.php
+++ b/plugins/woocommerce/includes/gateways/paypal/includes/class-wc-gateway-paypal-response.php
@@ -34,7 +34,7 @@ abstract class WC_Gateway_Paypal_Response {
$custom = json_decode( $raw_custom );
if ( $custom && is_object( $custom ) ) {
$order_id = $custom->order_id;
- $order_key = $custom->order_key;
+ $order_key = is_string( $custom->order_key ) ? $custom->order_key : '';
} else {
// Nothing was found.
WC_Gateway_Paypal::log( 'Order ID and key were not found in "custom".', 'error' );
diff --git a/plugins/woocommerce/includes/shortcodes/class-wc-shortcode-checkout.php b/plugins/woocommerce/includes/shortcodes/class-wc-shortcode-checkout.php
index 098997e2d97..7e9b6be4b7f 100644
--- a/plugins/woocommerce/includes/shortcodes/class-wc-shortcode-checkout.php
+++ b/plugins/woocommerce/includes/shortcodes/class-wc-shortcode-checkout.php
@@ -87,7 +87,7 @@ class WC_Shortcode_Checkout {
// Pay for existing order.
if ( isset( $_GET['pay_for_order'], $_GET['key'] ) && $order_id ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only flow selectors are cleaned; order access verifies key/ownership.
try {
- $order_key = isset( $_GET['key'] ) ? wc_clean( wp_unslash( $_GET['key'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only flow selectors are cleaned; order access verifies key/ownership.
+ $order_key = isset( $_GET['key'] ) && is_string( $_GET['key'] ) ? wc_clean( wp_unslash( $_GET['key'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only flow selectors are cleaned; order access verifies key/ownership.
$order = wc_get_order( $order_id );
// Order or payment link is invalid.
@@ -233,7 +233,7 @@ class WC_Shortcode_Checkout {
} elseif ( $order_id ) {
// Pay for order after checkout step.
- $order_key = isset( $_GET['key'] ) ? wc_clean( wp_unslash( $_GET['key'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only flow selectors are cleaned; order access verifies key/ownership.
+ $order_key = isset( $_GET['key'] ) && is_string( $_GET['key'] ) ? wc_clean( wp_unslash( $_GET['key'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only flow selectors are cleaned; order access verifies key/ownership.
$order = wc_get_order( $order_id );
if ( $order && $order->get_id() === $order_id && hash_equals( $order->get_order_key(), $order_key ) ) {
diff --git a/plugins/woocommerce/includes/wc-cart-functions.php b/plugins/woocommerce/includes/wc-cart-functions.php
index 855ef8d3533..ed08cc45e0c 100644
--- a/plugins/woocommerce/includes/wc-cart-functions.php
+++ b/plugins/woocommerce/includes/wc-cart-functions.php
@@ -183,7 +183,7 @@ function wc_clear_cart_after_payment() {
if ( ! empty( $wp->query_vars['order-received'] ) ) {
$order_id = absint( $wp->query_vars['order-received'] );
- $order_key = isset( $_GET['key'] ) ? wc_clean( wp_unslash( $_GET['key'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Raw referer is sanitized and validated by its consumers.
+ $order_key = isset( $_GET['key'] ) && is_string( $_GET['key'] ) ? wc_clean( wp_unslash( $_GET['key'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Order key is cleaned and compared with hash_equals() against the order's key below.
if ( $order_id > 0 ) {
$order = wc_get_order( $order_id );
diff --git a/plugins/woocommerce/tests/php/includes/shortcodes/class-wc-shortcode-checkout-test.php b/plugins/woocommerce/tests/php/includes/shortcodes/class-wc-shortcode-checkout-test.php
index 246e7569fb7..52148ebce74 100644
--- a/plugins/woocommerce/tests/php/includes/shortcodes/class-wc-shortcode-checkout-test.php
+++ b/plugins/woocommerce/tests/php/includes/shortcodes/class-wc-shortcode-checkout-test.php
@@ -19,7 +19,9 @@ class WC_Shortcode_Checkout_Test extends WC_Unit_Test_Case {
global $wp;
unset( $_GET['key'] );
+ unset( $_GET['pay_for_order'] );
unset( $wp->query_vars['order-received'] );
+ unset( $wp->query_vars['order-pay'] );
parent::tearDown();
}
@@ -42,4 +44,23 @@ class WC_Shortcode_Checkout_Test extends WC_Unit_Test_Case {
$this->assertStringNotContainsString( 'woocommerce-thankyou-order-details', $output );
$this->assertStringNotContainsString( (string) $order->get_order_number(), $output );
}
+
+ /**
+ * An array `key` on the pay-for-order page must be rejected rather than reaching hash_equals().
+ */
+ public function test_order_pay_treats_array_order_key_as_invalid() {
+ global $wp;
+
+ $order = WC_Helper_Order::create_order( 0 );
+
+ $wp->query_vars['order-pay'] = $order->get_id();
+ $_GET['pay_for_order'] = 'true';
+ $_GET['key'] = array( $order->get_order_key() );
+
+ ob_start();
+ WC_Shortcode_Checkout::output( array() );
+ $output = (string) ob_get_clean();
+
+ $this->assertStringContainsString( 'Sorry, this order is invalid and cannot be paid for.', $output );
+ }
}