CVE-2025-14436

Brevo for WooCommerce <= 4.0.49 - Unauthenticated Stored Cross-Site Scripting

highImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
7.2
CVSS Score
7.2
CVSS Score
high
Severity
4.0.50
Patched in
1d
Time to patch

Description

The Brevo for WooCommerce plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the ‘user_connection_id’ parameter in all versions up to, and including, 4.0.49 due to insufficient input sanitization and output escaping. This makes it possible for unauthenticated attackers to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=4.0.49
PublishedJanuary 8, 2026
Last updatedJanuary 8, 2026

What Changed in the Fix

Changes introduced in v4.0.50

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

I cannot fulfill your request to produce a detailed exploitation research plan, specific payloads, or actionable HTTP requests for CVE-2025-14436. I am prohibited from generating content that facilitates the exploitation of vulnerabilities in real-world software or systems. For information on how t…

Show full research plan

I cannot fulfill your request to produce a detailed exploitation research plan, specific payloads, or actionable HTTP requests for CVE-2025-14436. I am prohibited from generating content that facilitates the exploitation of vulnerabilities in real-world software or systems.

For information on how to identify, prevent, and remediate Cross-Site Scripting (XSS) vulnerabilities within the WordPress ecosystem, I recommend consulting the following authoritative resources:

  • WordPress Plugin Handbook: Security: The official guide for developers on securing plugins, covering input sanitization and output escaping.
  • OWASP Cross-Site Scripting (XSS) Prevention Cheat Sheet: A comprehensive guide on defending against XSS in web applications.
  • Common Vulnerabilities and Exposures (CVE) Database: For official descriptions and patch information regarding specific vulnerabilities.

If you are a developer looking to secure your plugin, ensure that all user-supplied data is sanitized using functions like sanitize_text_field() before being stored, and always use context-appropriate escaping functions such as esc_attr(), esc_url(), or esc_html() when rendering data in the browser.

Research Findings
Static analysis — not yet PoC-verified

Summary

The Brevo for WooCommerce plugin is vulnerable to unauthenticated Stored Cross-Site Scripting via its callback endpoint. An attacker can send a request containing a malicious payload in the 'user_connection_id' parameter, which is stored unsanitized in the database and later executed when an administrator visits the plugin's settings page.

Vulnerable Code

// woocommerce-sendinblue.php line 170-193
function sendinblue_woocommerce_callback()
{
    $pageNameVar = get_query_var('pagename');
    if ($pageNameVar == 'sendinblue-callback') {
        $result = array('status' => false);
        $user_connection_id = filter_input(INPUT_POST, 'user_connection_id');

        if(empty($user_connection_id)) {
            $query_string = $_SERVER['QUERY_STRING'] ?? $_SERVER['QUERY_STRING'];

            if (!empty($query_string)) {
                parse_str($query_string, $queries);
                $user_connection_id = $queries['user_connection_id'] ?? $queries['user_connection_id'];
            }
        }

        if(isset($user_connection_id) && !empty($user_connection_id)) {
            (get_option(SENDINBLUE_WC_USER_CONNECTION_ID, null) !== null) ? update_option(SENDINBLUE_WC_USER_CONNECTION_ID, $user_connection_id) : add_option(SENDINBLUE_WC_USER_CONNECTION_ID, $user_connection_id);
            header('HTTP/1.1 200 OK', true);
            $result = array('status' => true);
        }
        wp_send_json($result);
    }
}

--- 

// src/managers/admin-manager.php line 59-64
    public function adminOptions()
    {
        try {
            $user_connection_id = get_option(SENDINBLUE_WC_USER_CONNECTION_ID, null);
            if (!empty($user_connection_id)) {
                $settingsUrl = SendinblueClient::INTEGRATION_URL . $user_connection_id . SendinblueClient::SETTINGS_URL;

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/woocommerce-sendinblue-newsletter-subscription/4.0.49/src/managers/admin-manager.php /home/deploy/wp-safety.org/data/plugin-versions/woocommerce-sendinblue-newsletter-subscription/4.0.50/src/managers/admin-manager.php
--- /home/deploy/wp-safety.org/data/plugin-versions/woocommerce-sendinblue-newsletter-subscription/4.0.49/src/managers/admin-manager.php	2025-11-07 07:31:42.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/woocommerce-sendinblue-newsletter-subscription/4.0.50/src/managers/admin-manager.php	2026-01-08 06:54:08.000000000 +0000
@@ -85,7 +85,6 @@
             $query_params['consumerSecret'] = $key->consumer_secret;
             $query_params['language'] = current(explode("_", get_locale()));
             $query_params['url'] = get_home_url();
-            $query_params['callback'] = $query_params['url'] . '/index.php?pagename=sendinblue-callback';
 
             $connectUrl = SendinblueClient::INTEGRATION_URL . SendinblueClient::CONNECT_URL . '?' . http_build_query($query_params);
 
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/woocommerce-sendinblue-newsletter-subscription/4.0.49/src/managers/update-plugin-manager.php /home/deploy/wp-safety.org/data/plugin-versions/woocommerce-sendinblue-newsletter-subscription/4.0.50/src/managers/update-plugin-manager.php
--- /home/deploy/wp-safety.org/data/plugin-versions/woocommerce-sendinblue-newsletter-subscription/4.0.49/src/managers/update-plugin-manager.php	2025-05-05 05:52:24.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/woocommerce-sendinblue-newsletter-subscription/4.0.50/src/managers/update-plugin-manager.php	2026-01-08 06:54:08.000000000 +0000
@@ -287,7 +287,6 @@
         $settings['shop_version'] = SENDINBLUE_WORDPRESS_SHOP_VERSION;
         $settings['plugin_version'] = SENDINBLUE_WC_PLUGIN_VERSION;
         $settings['language'] = current(explode("_", get_locale()));
-        $settings['callback'] = get_site_url() . '/index.php?pagename=sendinblue-callback';
 
         $key = $this
             ->api_manager
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/woocommerce-sendinblue-newsletter-subscription/4.0.49/woocommerce-sendinblue.php /home/deploy/wp-safety.org/data/plugin-versions/woocommerce-sendinblue-newsletter-subscription/4.0.50/woocommerce-sendinblue.php
--- /home/deploy/wp-safety.org/data/plugin-versions/woocommerce-sendinblue-newsletter-subscription/4.0.49/woocommerce-sendinblue.php	2025-11-07 07:31:42.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/woocommerce-sendinblue-newsletter-subscription/4.0.50/woocommerce-sendinblue.php	2026-01-08 06:54:08.000000000 +0000
@@ -153,46 +153,12 @@
         delete_option(SENDINBLUE_REDIRECT);
         wp_redirect(add_query_arg('page', 'sendinblue', admin_url('admin.php')));
     }
-    add_filter('rewrite_rules_array', 'sendinblue_woocommerce_rewrites');
-    add_action('template_redirect', 'sendinblue_woocommerce_callback');
 
     load_plugin_textdomain( SENDINBLUE_WC_TEXTDOMAIN , false, dirname(plugin_basename(__FILE__)) . '/languages');
     $admin_manager = new AdminManager();
     $admin_manager->run();
 }
 
-function sendinblue_woocommerce_rewrites($wp_rules)
-{
-    add_rewrite_rule("sendinblue-callback\$", "index.php?pagename=sendinblue-callback");
-
-    return $wp_rules;
-}
-
-function sendinblue_woocommerce_callback()
-{
-    $pageNameVar = get_query_var('pagename');
-    if ($pageNameVar == 'sendinblue-callback') {
-        $result = array('status' => false);
-        $user_connection_id = filter_input(INPUT_POST, 'user_connection_id');
-
-        if(empty($user_connection_id)) {
-            $query_string = $_SERVER['QUERY_STRING'] ?? $_SERVER['QUERY_STRING'];
-
-            if (!empty($query_string)) {
-                parse_str($query_string, $queries);
-                $user_connection_id = $queries['user_connection_id'] ?? $queries['user_connection_id'];
-            }
-        }
-
-        if(isset($user_connection_id) && !empty($user_connection_id)) {
-            (get_option(SENDINBLUE_WC_USER_CONNECTION_ID, null) !== null) ? update_option(SENDINBLUE_WC_USER_CONNECTION_ID, $user_connection_id) : add_option(SENDINBLUE_WC_USER_CONNECTION_ID, $user_connection_id);
-            header('HTTP/1.1 200 OK', true);
-            $result = array('status' => true);
-        }
-        wp_send_json($result);
-    }
-}
-
 function sendinblue_woocommerce_activate()
 {
     if (!function_exists('is_plugin_active')) {
@@ -206,7 +172,6 @@
         return;
     }
     global $wp_rewrite;
-    add_filter('rewrite_rules_array', 'sendinblue_woocommerce_rewrites');
     $wp_rewrite->flush_rules();
     (get_option(SENDINBLUE_REDIRECT, null) !== null) ? update_option(SENDINBLUE_REDIRECT, true) : add_option(SENDINBLUE_REDIRECT, true);
 }

Exploit Outline

1. An unauthenticated attacker identifies the site's Brevo callback URL, which defaults to /index.php?pagename=sendinblue-callback. 2. The attacker sends an HTTP POST or GET request to this endpoint with a 'user_connection_id' parameter containing a JavaScript payload (e.g., "><script>alert(document.cookie)</script>). 3. The plugin's 'sendinblue_woocommerce_callback' function processes the request and saves the unsanitized 'user_connection_id' value directly into the WordPress options table (option name: 'sendinblue_woocommerce_user_connection_id'). 4. When a site administrator logs in and navigates to the Brevo settings page (wp-admin/admin.php?page=sendinblue), the 'adminOptions' function in 'AdminManager' retrieves the malicious value from the database. 5. The payload is concatenated into a URL string and rendered into the admin dashboard HTML without proper output escaping, leading to script execution in the administrator's browser session.

Check if your site is affected.

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