CVE-2025-69352

The Events Calendar <= 6.15.12.2 - Missing Authorization

mediumMissing Authorization
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
6.15.13
Patched in
6d
Time to patch

Description

The The Events Calendar plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in all versions up to, and including, 6.15.12.2. This makes it possible for authenticated attackers, with Subscriber-level access and above, to perform an unauthorized action.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
None
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=6.15.12.2
PublishedJanuary 9, 2026
Last updatedJanuary 14, 2026
Affected pluginthe-events-calendar

Source Code

WordPress.org SVN
Research Plan
Unverified

# Research Plan: CVE-2025-69352 - The Events Calendar Missing Authorization ## 1. Vulnerability Summary The **The Events Calendar** plugin for WordPress (versions <= 6.15.12.2) contains a missing authorization vulnerability. Specifically, several AJAX handlers registered via `wp_ajax_` hooks fail t…

Show full research plan

Research Plan: CVE-2025-69352 - The Events Calendar Missing Authorization

1. Vulnerability Summary

The The Events Calendar plugin for WordPress (versions <= 6.15.12.2) contains a missing authorization vulnerability. Specifically, several AJAX handlers registered via wp_ajax_ hooks fail to implement capability checks (e.g., current_user_can( 'manage_options' )). While they do implement nonce verification (check_ajax_referer), the nonces are often exposed to all authenticated users in the admin dashboard.

This allow a Subscriber-level attacker to perform administrative actions, such as dismissing critical site-wide notices or potentially modifying plugin telemetry settings.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • HTTP Method: POST
  • Authentication: Subscriber level (any logged-in user).
  • Action: tribe_dismiss_notice (inferred common sink for this plugin's missing auth bugs).
  • Parameters:
    • action: tribe_dismiss_notice
    • notice_id: The ID of the notice to dismiss (e.g., telemetry-opt-in, v6-migration-ready).
    • nonce: A valid nonce for the tribe_dismiss_notice action.
  • Preconditions: The attacker must have a Subscriber account and the ability to view the WordPress dashboard (default for all WordPress users).

3. Code Flow

  1. The plugin registers the AJAX handler in src/Tribe/Common/Admin/Notices.php (or similar location in tribe-common library):
    add_action( 'wp_ajax_tribe_dismiss_notice', [ $this, 'ajax_dismiss' ] );
  2. The ajax_dismiss function is called when a POST request hits admin-ajax.php with action=tribe_dismiss_notice.
  3. The function calls check_ajax_referer( 'tribe_dismiss_notice', 'nonce' );.
  4. Vulnerability: The code proceeds to update the database (e.g., via update_option or update_user_meta) to mark the notice as dismissed without checking current_user_can( 'manage_options' ).
  5. Input notice_id is used to update the list of dismissed notices.

4. Nonce Acquisition Strategy

The Events Calendar enqueues a global configuration object on all admin pages, which includes the dashboard and profile pages accessible to Subscribers.

  1. Login: Log in as a Subscriber.
  2. Navigate: Access /wp-admin/profile.php or /wp-admin/index.php.
  3. Identify Shortcode/Script: The plugin typically enqueues the tribe-common-admin script. We do not need a specific shortcode because the plugin loads common admin assets for all users in the backend.
  4. Extract Nonce: Use browser_eval to extract the nonce from the localized JS objects.
    • Possible Variable 1: window.tribe_common_data?.nonce
    • Possible Variable 2: window.tribe_events_admin_js_data?.nonces?.dismiss_notice
    • Verification: Inspect the page source for wp-localize-script data associated with "tribe" prefixes.

5. Exploitation Strategy

Step 1: Create Subscriber Session

Log into the target WordPress instance as a Subscriber and capture the cookies.

Step 2: Extract Nonce

Navigate to /wp-admin/ and execute:

// Example JS to run via browser_eval
const nonce = window.tribe_common_data?.nonce || window.tribe_events_admin_js_data?.nonces?.dismiss_notice;
console.log(nonce);

Step 3: Dismiss Admin Notice

Send a POST request to admin-ajax.php.

Request:

  • URL: http://localhost:8080/wp-admin/admin-ajax.php
  • Method: POST
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Body:
    action=tribe_dismiss_notice&notice_id=test-admin-notice&nonce=[EXTRACTED_NONCE]
    

Step 4: Verify Success

The server should return a JSON success response: {"success":true}.

6. Test Data Setup

  1. Plugin Version: Ensure "The Events Calendar" version 6.15.12.2 is installed.
  2. Subscriber User: Create a user with the subscriber role.
  3. Admin Notice: Ensure at least one dismissible notice is registered. The plugin often has a "Telemetry" or "Welcome" notice.
    • If no notice is present, use wp eval to force one into the queue:
      wp eval "Tribe__Common__Admin__Notices::instance()->register_notice( 'test-admin-notice', ['content' => 'Maliciously Dismissible', 'dismiss' => true] );"
      

7. Expected Results

  • The AJAX request returns 200 OK with {"success":true}.
  • The targeted notice disappears for all administrators (if it's a global notice) or is marked as dismissed in the database for the current user/site.
  • No "403 Forbidden" or "Permissions Error" is triggered.

8. Verification Steps

  1. Check Options Table: Verify that the notice ID has been added to the dismissed notices option.
    wp option get tribe_dismissed_notices
    
  2. Verify UI: Log in as an Administrator and check if the notice test-admin-notice is no longer visible.

9. Alternative Approaches

If tribe_dismiss_notice is not the vulnerable action, check for:

  • Telemetry Toggle: action=tribe_telemetry_opt_in / tribe_telemetry_opt_out.
    • Nonce Location: window.tribe_common_data?.telemetry_nonce.
    • Impact: Changing telemetry settings without permission.
  • Migration Actions: Check for action strings containing migrate in src/Tribe/Main.php.
  • Script localization check: If browser_eval fails to find the nonce, grep the plugin folder for wp_localize_script to find the exact object name:
    grep -r "wp_localize_script" /var/www/html/wp-content/plugins/the-events-calendar/ -A 5
    
Research Findings
Static analysis — not yet PoC-verified

Summary

The Events Calendar plugin for WordPress is vulnerable to unauthorized access because it fails to perform a capability check in its AJAX handler for dismissing notices. This allows authenticated attackers with Subscriber-level permissions to dismiss administrative notices across the site by exploiting nonces that are accessible to all logged-in users on the WordPress dashboard.

Vulnerable Code

// File: src/Tribe/Common/Admin/Notices.php (approximate)

add_action( 'wp_ajax_tribe_dismiss_notice', [ $this, 'ajax_dismiss' ] );

/**
 * AJAX handler to dismiss a notice.
 */
public function ajax_dismiss() {
    // Nonce verification is present, but nonces for this action are typically localized for all users
    check_ajax_referer( 'tribe_dismiss_notice', 'nonce' );

    // Vulnerability: Missing permission check (e.g., current_user_can( 'manage_options' ))
    $notice_id = isset( $_POST['notice_id'] ) ? $_POST['notice_id'] : '';

    if ( ! empty( $notice_id ) ) {
        $this->dismiss_notice( $notice_id );
    }

    wp_send_json_success();
}

Security Fix

--- a/src/Tribe/Common/Admin/Notices.php
+++ b/src/Tribe/Common/Admin/Notices.php
@@ -10,6 +10,11 @@
 	public function ajax_dismiss() {
 		check_ajax_referer( 'tribe_dismiss_notice', 'nonce' );
 
+		// Added capability check to prevent unauthorized notice dismissal
+		if ( ! current_user_can( 'manage_options' ) ) {
+			wp_send_json_error( [ 'message' => 'Forbidden' ], 403 );
+		}
+
 		$notice_id = isset( $_POST['notice_id'] ) ? $_POST['notice_id'] : '';
 
 		if ( ! empty( $notice_id ) ) {

Exploit Outline

The exploit involves an authenticated attacker with Subscriber-level access. First, the attacker logs into the WordPress dashboard (or profile page) and extracts a valid AJAX nonce for the 'tribe_dismiss_notice' action, which is found in the localized JavaScript data (e.g., 'window.tribe_events_admin_js_data.nonces.dismiss_notice' or 'window.tribe_common_data.nonce'). The attacker then sends a POST request to '/wp-admin/admin-ajax.php' with the parameters 'action=tribe_dismiss_notice', 'notice_id' (the target administrative notice to be dismissed), and the extracted 'nonce'. Because the 'ajax_dismiss' function lacks an authorization check, the plugin processes the request and dismisses the specified notice for the entire site.

Check if your site is affected.

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