CVE-2025-9989

Broadstreet <= 1.53.1 - Authenticated (Admin+) Stored Cross-Site Scripting

mediumImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
4.4
CVSS Score
4.4
CVSS Score
medium
Severity
1.53.2
Patched in
1d
Time to patch

Description

The Broadstreet plugin for WordPress is vulnerable to Stored Cross-Site Scripting via admin settings in all versions up to, and including, 1.53.1 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with administrator-level permissions and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page. This only affects multi-site installations and installations where unfiltered_html has been disabled.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=1.53.1
PublishedMay 12, 2026
Last updatedMay 13, 2026
Affected pluginbroadstreet

What Changed in the Fix

Changes introduced in v1.53.2

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan targets a Stored Cross-Site Scripting (XSS) vulnerability in the **Broadstreet** plugin (<= 1.53.1). The vulnerability allows high-privileged users (Administrators) to inject malicious scripts into admin settings, which is particularly critical in multi-site environments or harden…

Show full research plan

This research plan targets a Stored Cross-Site Scripting (XSS) vulnerability in the Broadstreet plugin (<= 1.53.1). The vulnerability allows high-privileged users (Administrators) to inject malicious scripts into admin settings, which is particularly critical in multi-site environments or hardened installations where unfiltered_html is disabled.


1. Vulnerability Summary

  • Vulnerability: Authenticated (Admin+) Stored XSS
  • Location: Admin settings page (typically Settings -> Broadstreet)
  • Cause: The plugin fails to sanitize user-provided configuration values before storing them and fails to escape those values when rendering them back in the administration interface.
  • Affected Versions: <= 1.53.1
  • Patch: Version 1.53.2 introduces sanitization (e.g., sanitize_text_field) and escaping (e.g., esc_attr).

2. Attack Vector Analysis

  • Endpoint: wp-admin/options-general.php?page=broadstreet-settings (inferred slug) or a custom POST handler in Broadstreet_Core.
  • Vulnerable Parameter: Likely parameters related to the "Access Token" or other configuration settings handled by Broadstreet_Config.
  • Authentication: Requires Administrator privileges.
  • Precondition: unfiltered_html must be disabled (e.g., in a Multi-site environment or by defining DISALLOW_UNFILTERED_HTML in wp-config.php). This prevents the default behavior where Admins can post raw HTML.

3. Code Flow

  1. Entry Point: The plugin registers an admin menu via Broadstreet_Core (referenced in broadstreet.php).
  2. Configuration Storage: When an admin saves settings, the plugin likely captures $_POST data and uses Broadstreet_Config::set($key, $value) or update_option().
    • Source Path: Broadstreet/Config.php contains the setValue($key, $value) method which stores configuration in the $_config array.
  3. Persistence: The configuration is persisted to the database (likely as a single option or individual options).
  4. Sink: When the settings page is reloaded, the plugin retrieves values using Broadstreet_Config::get($key). The values are echoed into the HTML (e.g., inside an <input> tag's value attribute or as text) without using esc_attr() or esc_html().

4. Nonce Acquisition Strategy

The Broadstreet plugin settings page uses WordPress standard security practices for admin forms.

  1. Identify Page: The settings page is located at /wp-admin/options-general.php?page=broadstreet-settings.
  2. Navigation: Use browser_navigate to access the settings page as an Administrator.
  3. Extraction: The form will contain a hidden field for the nonce.
    • JS Variable (Inferred): Broadstreet may localize data. Check for window.broadstreet_data?.nonce.
    • DOM Extraction: Use browser_eval to extract the _wpnonce field from the settings form.
    • Action String: The nonce action is likely broadstreet-options or update.

Extraction Command:

browser_eval("document.querySelector('input[name=\"_wpnonce\"]')?.value || document.querySelector('#_wpnonce')?.value")

5. Exploitation Strategy

The goal is to inject a payload into an admin setting field that executes when the admin page is viewed.

  1. Preparation:
    • Log in as Administrator.
    • Ensure DISALLOW_UNFILTERED_HTML is true to simulate the vulnerable environment.
  2. Identify Form Fields: Inspect the HTML at the settings page to find the specific parameter names (e.g., broadstreet_access_token or broadstreet_options[access_token]).
  3. Craft Payload:
    • Standard attribute breakout: "><script>alert(document.domain)</script>
    • If inside an attribute: x" onmouseover="alert(1)"
  4. Send Exploit Request: Use http_request to send a POST request mimicking the settings save action.

Example Request (Template):

  • Method: POST
  • URL: http://localhost:8080/wp-admin/options.php (if using Settings API) or the plugin's own slug.
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Body:
    option_page=broadstreet_options&
    action=update&
    _wpnonce=[EXTRACTED_NONCE]&
    broadstreet_access_token="><script>alert('XSS')</script>
    

6. Test Data Setup

  1. Installation: wp plugin install broadstreet --version=1.53.1 --activate
  2. Hardening: Add define( 'DISALLOW_UNFILTERED_HTML', true ); to wp-config.php to ensure the XSS is not "by design."
  3. User Creation: Create a standard admin user if not present.

7. Expected Results

  • Upon submitting the POST request, the server should return a 302 Redirect back to the settings page.
  • When the admin page is subsequently loaded, the HTML source of the configuration input will look like:
    <input ... value=""><script>alert('XSS')</script>">
  • The browser will execute the script, triggering an alert box.

8. Verification Steps

  1. Database Check: Use WP-CLI to verify the payload is stored raw in the options table.
    wp option get broadstreet_options
    # OR if stored individually
    wp option get broadstreet_access_token
    
  2. Response Inspection: Verify the unescaped output in the response body via http_request (GET).
    # Look for the raw payload in the HTML response
    grep -F "<script>alert('XSS')</script>"
    

9. Alternative Approaches

  • Settings API Bypass: If the plugin does not use options.php, check for an admin_init hook that handles settings directly. Search for if (isset($_POST['...'])) in the plugin source (specifically in Broadstreet/Core.php if available).
  • Different Fields: If the "Access Token" field is sanitized, try other fields like "Zone IDs" or "Keyword Settings" if the plugin provides them on the settings page.
  • AJAX Handler: Check for any wp_ajax_ actions that might be used to save individual settings, which could be vulnerable if they call Broadstreet_Config::set() without sanitization.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Broadstreet plugin for WordPress (<= 1.53.1) is vulnerable to Stored Cross-Site Scripting (XSS) because it fails to sanitize and escape administrative settings data. Authenticated attackers with Administrator permissions can inject malicious scripts into configuration fields like the Access Token, which will execute when any administrator views the plugin's settings page, specifically in environments where 'unfiltered_html' is restricted.

Vulnerable Code

// Broadstreet/Config.php line 107
    public function setValue($key, $value)
    {
        return $this->_config[$key] = $value;
    }

---

// Broadstreet/Config.php line 119
    public function getValue($key = FALSE, $default = FALSE)
    {
        if($key === FALSE)
            return $this->_config;

        $config = $this->_config;
        $keys   = explode('.', $key);

        foreach($keys as $key)
        {
            if(array_key_exists($key, $config))
                $config = $config[$key];
            else
                return $default;
        }

        return $config;
    }

Security Fix

--- Broadstreet/Config.php
+++ Broadstreet/Config.php
@@ -109,7 +109,7 @@
      */
     public function setValue($key, $value)
     {
-        return $this->_config[$key] = $value;
+        return $this->_config[$key] = sanitize_text_field($value);
     }
 
     /**
@@ -137,6 +137,6 @@
                 return $default;
         }
 
-        return $config;
+        return is_string($config) ? esc_attr($config) : $config;
     }

Exploit Outline

1. Log into the WordPress admin panel with Administrator privileges on a site with 'unfiltered_html' disabled (e.g., a Multisite environment). 2. Navigate to the Broadstreet settings page (typically Settings -> Broadstreet). 3. Identify a configuration input field such as the 'Access Token' and extract the required security nonce (`_wpnonce`) from the page source. 4. Craft a POST request to update the settings, injecting an XSS payload into the target field: `"><script>alert(document.domain)</script>`. 5. Upon submission, the plugin saves the raw script to the database. The script will execute whenever the settings page is rendered in an administrator's browser, as the value is echoed into the HTML source without proper output escaping.

Check if your site is affected.

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