WordPress Plugin Vulnerabilities
Brave Popup Builder < 0.8.6 - Subscriber+ Unpublished Popup Disclosure via Preview
Description
Brave Popup Builder (slug: brave-popup-builder) has a broken access control issue in versions through 0.8.5. Any logged-in user - Subscriber or WooCommerce Customer is enough — can read popup content they shouldn't have access to by passing a post ID in the URL.
The preview feature lives in bravepop_render_popup() in lib/render.php, hooked to wp_head at priority 9. When brave_popup is present in the query string, the function checks is_user_logged_in() and nothing else: no capability check, no ownership check, no post_status check. It then instantiates BravePop_Popup and renders:
add_action('wp_head', 'bravepop_render_popup', 9);
function bravepop_render_popup() {
$brave_popupID = filter_input(INPUT_GET, 'brave_popup');
$brave_popupStep = filter_input(INPUT_GET, 'popup_step');
// Popup Preview
if ($brave_popupID && is_user_logged_in()) {
return new BravePop_Popup($brave_popupID, 'popup', true, $brave_popupStep ? absint($brave_popupStep) : false);
}
// Normal render path below only loads publish/scheduled popups;
// this branch returns early and skips all of that.
}
Popup posts use the standard WordPress post ID sequence (CPT registered with capability_type => 'post'). An attacker can walk through IDs with ?brave_popup={id} and pull back anything that has popup_data meta attached — drafts, targeting-restricted campaigns, the lot. The CPT is also registered with publicly_queryable => false, so there is no normal front-end permalink for a popup. In practice, this preview parameter is how popup HTML gets rendered on the front end, which makes the missing auth check bite harder for non-public content.
What a Subscriber can actually get:
Draft popups - confirmed. I did not separately test pending, private, or scheduled statuses, but the code never looks at post_status in this path, so they should behave the same way.
Published popups that targeting rules would normally hide. The preview branch in Popup.php forces userTypeMatch, refererMatch, countryMatch, languageMatch, utmMatch, and bodyClassMatch all to true when the preview flag is set, and because bravepop_render_popup() returns before page-placement resolution runs, audience filters, geo/language/UTM/referrer restrictions, and placement rules all get skipped.
Depending on how the popup is built, the leaked popup_data can expose unreleased campaigns, coupon codes not meant to be public yet, hidden form fields, and sometimes integration endpoints or newsletter list IDs buried in the configuration.
-----------------------
Not CVE-2025-68508
CVE-2025-68508 was fixed in 0.8.4 (changelog: "Draft Popup Content could be loaded by anyone using custom Javascript", CVSS 5.3). That one was unauthenticated: bravepop_ajax_load_popup_content, registered under wp_ajax_nopriv_bravepop_ajax_load_popup_content. The 0.8.4 patch added a post_status gate and a current_user_can('manage_options') check to that AJAX handler only.
This is a separate code path, still open in 0.8.5:
CVE-2025-68508 (fixed 0.8.4):
Function: bravepop_ajax_load_popup_content
Hook: wp_ajax_nopriv_bravepop_ajax_load_popup_content
Method: POST (AJAX)
Boundary: unauthenticated
Type: authentication bypass
This issue (0.8.5):
Function: bravepop_render_popup
Hook: wp_head
Method: GET
Boundary: any logged-in user (Subscriber+)
Type: IDOR / missing object-level authorization
The is_user_logged_in() block in lib/render.php is the same in 0.8.3, 0.8.4, and 0.8.5. The 0.8.4 security release only touched the AJAX handler in that file. The GET preview branch never got a capability or post_status check and is still reachable by Subscribers on the current release.