On August 21 and August 22, 2026, Wordfence Argus, created by the Wordfence Threat Intelligence team, identified two independent critical vulnerability chains in The Events Calendar, a WordPress plugin active on more than 600,000 websites. Both chains begin in the plugin’s widget-rendering pipeline and can ultimately lead to Remote Code Execution without authentication through two separate methods.
The first chain uses PHP Object Injection to execute arbitrary operating system commands on the underlying server. The second chain bypasses the object-injection guard and abuses an arbitrary-callable primitive to reset an administrator’s password, after which an attacker can upload a malicious plugin and take complete control of the site.
No login, account registration, or social engineering is required, though the target event page must have comments enabled, which also requires The Events Calendar’s own “Show comments on event pages” option to be active. Both chains can be triggered through WordPress’s pending-comment preview without moderator approval. Successful exploitation could lead to complete site takeover, sensitive data theft, malware deployment, and a total loss of confidentiality, integrity, and availability.
Wordfence Premium, Wordfence Care, and Wordfence Response users received a firewall rule protecting against known exploits targeting both vulnerabilities on August 22, 2026. Sites using the free version of Wordfence will receive the same protection 30 days later, on September 21, 2026.
We sent full disclosure details for the first vulnerability to StellarWP, the developer of The Events Calendar, on August 21, 2026, through the Wordfence Vulnerability Management Portal. The StellarWP team acknowledged the report on August 24, 2026 and released an initial patch on August 25, 2026, just four days after our initial disclosure.
We disclosed the second vulnerability on August 23, 2026, after validating the report and confirming the proof-of-concept exploit. StellarWP acknowledged it on August 24, 2026 and released a fully patched version on September 10, 2026. We commend the StellarWP team for its prompt response and rapid work to address both critical issues.
We strongly urge users to update The Events Calendar to the latest patched version, version 6.17.4.1 at the time of publication, as soon as possible.
Vulnerability Summaries from Wordfence Intelligence
Technical Analysis
The two vulnerabilities are independent exploitation chains built on the same dangerously exposed widget-rendering pipeline. A combination of insecure design decisions allows an anonymous commenter to place attacker-controlled Gutenberg block markup into a single-event page, obtain a valid integrity hash for a malicious widget instance, and reach one of two execution sinks.
The first chain smuggles a serialized object past a flawed validation routine and triggers a dangerous deserialization gadget. The second uses a valid plain-array payload to bypass the object guard entirely, inject attacker-controlled template variables, and invoke an arbitrary PHP callable.
The Shared Attack Surface: Processing Comments as Gutenberg Blocks
Both chains begin in the plugin’s V2 single-event template. In get_v1_single_event_html(), located in src/Tribe/Views/V2/Template_Bootstrap.php, the plugin uses output buffering to capture the entire rendered page, including the comment section, and then passes that buffer through WordPress’s do_blocks() function:
protected function get_v1_single_event_html() {
// ...
ob_start();
if ( 'page' === $setting ) {
echo '<section id="tribe-events">';
} else {
echo '<section id="tribe-events-pg-template" class="tribe-events-pg-template">';
}
tribe_events_before_html();
tribe_get_view( 'single-event' );
tribe_events_after_html();
echo '</section>';
$html = ob_get_clean();
if ( function_exists( 'do_blocks' ) ) {
$html = do_blocks( $html );
}
return $html;
}
WordPress core does not run do_blocks() over comment text; it processes blocks in post content. By buffering the entire page and passing it through do_blocks(), The Events Calendar dramatically widens the block-parser attack surface to include content submitted by anonymous comment authors.
WordPress’s KSES comment sanitizer preserves HTML comment delimiters (<!-- ... -->), which Gutenberg uses as block markup. A malicious block embedded in a comment can therefore survive sanitization and reach the block parser. After a comment is submitted, WordPress ordinarily redirects the commenter to a moderation-hash URL (?unapproved=N&moderation-hash=H) that allows the author to preview their own pending comment immediately. As a result, neither chain requires administrator approval, the attacker triggers widget rendering simply by viewing their own pending comment through the moderation-hash preview URL. Both the object-injection and arbitrary-callable sinks fire from this unapproved-comment preview.
The Shared Integrity-Check Bypass
Once do_blocks() encounters a wp:legacy-widget block whose idBase begins with tribe-widget-, The Events Calendar’s enable_rendering_widget_copied() filter in src/Tribe/Views/V2/Widgets/Service_Provider.php runs. The method base64-decodes the attacker-supplied widget instance, passes it to is_safe_widget_instance(), and, if the check succeeds, replaces the supplied hash with a freshly computed wp_hash() of the attacker’s data:
public function enable_rendering_widget_copied( $parsed_block ) {
if ( ! isset( $parsed_block['attrs']['idBase'] ) ) {
return $parsed_block;
}
$widget_id = $parsed_block['attrs']['idBase'];
if ( ! str_starts_with( $widget_id, 'tribe-widget-' ) ) {
return $parsed_block;
}
$instance = $parsed_block['attrs']['instance'] ?? [];
if ( ! isset( $instance['encoded'], $instance['hash'] ) ) {
return $parsed_block;
}
$serialized_instance = base64_decode( $instance['encoded'] );
// Skip instances that do not pass validation.
if ( ! $this->is_safe_widget_instance( $serialized_instance ) ) {
return $parsed_block;
}
$instance['hash'] = wp_hash( $serialized_instance );
$parsed_block['attrs']['instance'] = $instance;
return $parsed_block;
}
WordPress core uses wp_hash() over a serialized widget instance as an integrity check before deserializing it. By generating a valid hash for attacker-controlled data, the plugin defeats core’s only integrity guard. An attacker can place any value, such as "hash":"deadbeef", in the block markup, and The Events Calendar silently replaces it with a cryptographically valid hash before core inspects the instance.
From this shared point, the two exploitation paths diverge.
Vulnerability Chain One: PHP Object Injection to Remote Code Execution
The first chain exploits a critical flaw in is_safe_widget_instance():
protected function is_safe_widget_instance( $serialized ) {
$data = is_string( $serialized )
// phpcs WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize
? @unserialize( $serialized, [ 'allowed_classes' => false ] )
: false;
return ! $this->contains_object( $data );
}
The method calls unserialize() with allowed_classes => false and checks whether the returned value contains a PHP object. If it does not, the widget instance is considered safe. This logic assumes that allowed_classes => false prevents object-related behavior during the preliminary parse, but that assumption is incorrect: PHP can invoke the __unserialize() and __wakeup() magic methods while parsing, before unserialize() returns.
An attacker can exploit this behavior by placing a complete, well-formed object inside a serialized structure and appending an invalid type token after it. A simplified structure looks like this:
a:2:{i:0;<gadget_object>i:1;X}
The preliminary parse encounters the malformed tail and returns false. The guard then evaluates contains_object( false ), finds no object in the returned value, and declares the payload safe. Later, when WordPress core’s legacy-widget renderer performs the real unserialize() call, PHP constructs the embedded object and triggers its magic methods before failing on the trailing invalid data. By then, the dangerous behavior has already occurred.
The Deserialization Gadget
The magic-method entry point is __unserialize() in common/src/Tribe/Utils/Collection_Trait.php, which is used by Lazy_Post_Collection:
public function __unserialize( array $data ): void {
if ( method_exists( $this, 'custom_unserialize' ) ) {
$this->items = $this->custom_unserialize( maybe_serialize( $data ) );
return;
}
$this->items = $data;
}
This method dispatches directly to custom_unserialize() in src/Tribe/Collections/Lazy_Post_Collection.php, which contains the final execution sink:
protected function custom_unserialize( $serialized ) {
$unserialized = unserialize( $serialized );
if ( false === $unserialized || ! is_array( $unserialized ) ) {
return null;
}
return array_map( $unserialized['callback'], $unserialized['ids'] );
}
The method deserializes attacker-controlled data and passes $unserialized['callback'] and $unserialized['ids'] directly to array_map() without validation. By setting callback to system and ids to an array containing a shell command, such as id or cat /etc/passwd, an attacker can execute arbitrary operating system commands as the web server user.
This chain provides direct Remote Code Execution and can result in complete compromise of the affected WordPress installation.
Vulnerability Chain Two: Arbitrary PHP Callable to Remote Code Execution
The second chain reaches the same widget-instance validation routine but does not require a serialized object. It instead uses a plain PHP array, which passes is_safe_widget_instance() truthfully and completely because it contains no object. The object-injection guard therefore provides no protection against this exploitation path.
After The Events Calendar generates a valid hash for the array, WordPress core deserializes the instance and passes it to the widget’s widget() method. Widget_Abstract::setup_arguments() merges the attacker-controlled array directly into $this->arguments with array_merge().
The template engine later calls extract( $this->context ) on line 1066 of common/src/Tribe/Template.php. This turns every attacker-controlled array key, including classes, into a local variable in the template scope.
An attacker can force the widget’s event query to return no results, for example by appending ?tribe_paged=99 to the URL. This causes the messages.php sub-template to load. The template merges the attacker-controlled $classes variable with a default class list and passes the result to tec_classes():
if ( empty( $messages ) ) {
return;
}
global $wp_version;
$default_classes = [
'tribe-events-header__messages',
'tribe-events-c-messages',
'tribe-common-b2',
];
$classes = isset( $classes ) ? array_merge( $default_classes, $classes ) : $default_classes;
$attributes = isset( $attributes ) ? (array) $attributes : [];
The tec_classes() function forwards this merged array to TribeUtilsElement_Classes::parse_array(). The utility is intended to collect CSS class names for widget markup and supports Closure values for dynamic class generation. The critical flaw is that its callable check is not restricted to closures. It accepts any value for which PHP’s is_callable() returns true, including strings naming globally available functions:
protected function parse_array( array $values ) {
foreach ( $values as $key => $value ) {
if ( is_int( $key ) ) {
if ( is_bool( $value ) ) {
$this->parse( $key, $value );
} else {
$this->parse( $value );
}
} elseif ( is_string( $key ) ) {
if ( $value instanceof Closure || is_callable( $value ) ) {
$value = $value( $this->results );
}
$this->parse_string( $key, tribe_is_truthy( $value ) );
}
}
}
Because is_callable( 'wp_update_user' ) returns true, an attacker can construct a classes map containing entries equivalent to:
{
"ID": true,
"user_pass": true,
"zz": "wp_update_user"
}
As parse_array() processes the map in insertion order, it adds the ID and user_pass keys to $this->results with boolean true values. When the loop reaches the zz entry, is_callable( 'wp_update_user' ) succeeds and the function invokes wp_update_user( $this->results ).
At that point, $this->results contains values equivalent to ['ID' => true, 'user_pass' => true, ...]. WordPress interprets this as an instruction to change the password of user ID 1 to the string 1. The in-process call to wp_update_user() does not perform a capability check. The attacker can then log in as the administrator and upload a malicious plugin, achieving full Remote Code Execution and complete site takeover.
Disclosure Timeline
Vendor / external action
Conclusion
In this post, we detailed two independent critical vulnerability chains in The Events Calendar affecting vulnerable releases up to and including version 6.17.4. The first allows an unauthenticated attacker to execute arbitrary operating system commands through PHP Object Injection. The second allows an unauthenticated attacker to invoke arbitrary PHP functions with attacker-controlled arguments, which can be used to reset an administrator’s password and upload a malicious plugin.
Both chains arise from the plugin processing attacker-controlled comment content as Gutenberg blocks, generating valid integrity hashes for attacker-supplied widget instances, and passing that data into unsafe downstream behavior. Any affected site with comments enabled on event pages is at direct risk from anonymous attackers on the internet.
We strongly encourage all WordPress site owners and administrators using The Events Calendar to verify that they are running the latest patched version, 6.17.4.1, immediately. Neither chain requires a login or account registration, and both can be triggered without moderator approval through the pending-comment preview flow. Successful attacks can result in complete site and server compromise.
Wordfence Premium, Wordfence Care, and Wordfence Response users received a firewall rule protecting against known exploits targeting both vulnerabilities on August 22, 2026. Sites using the free version of Wordfence will receive the same protection 30 days later, on September 21, 2026.
If you know someone who uses The Events Calendar on a WordPress site, we strongly recommend sharing this advisory with them so they can update promptly and keep their site secure.
The post Wordfence Argus Identifies Two Critical Unauthenticated Vulnerability Chains Leading to Remote Code Execution in The Events Calendar Plugin appeared first on Wordfence.