The Events Calendar <= 6.15.12.2 - Missing Authorization
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:NTechnical Details
<=6.15.12.2Source Code
WordPress.org SVN# 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_noticenotice_id: The ID of the notice to dismiss (e.g.,telemetry-opt-in,v6-migration-ready).nonce: A valid nonce for thetribe_dismiss_noticeaction.
- Preconditions: The attacker must have a Subscriber account and the ability to view the WordPress dashboard (default for all WordPress users).
3. Code Flow
- The plugin registers the AJAX handler in
src/Tribe/Common/Admin/Notices.php(or similar location intribe-commonlibrary):add_action( 'wp_ajax_tribe_dismiss_notice', [ $this, 'ajax_dismiss' ] ); - The
ajax_dismissfunction is called when a POST request hitsadmin-ajax.phpwithaction=tribe_dismiss_notice. - The function calls
check_ajax_referer( 'tribe_dismiss_notice', 'nonce' );. - Vulnerability: The code proceeds to update the database (e.g., via
update_optionorupdate_user_meta) to mark the notice as dismissed without checkingcurrent_user_can( 'manage_options' ). - Input
notice_idis 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.
- Login: Log in as a Subscriber.
- Navigate: Access
/wp-admin/profile.phpor/wp-admin/index.php. - Identify Shortcode/Script: The plugin typically enqueues the
tribe-common-adminscript. We do not need a specific shortcode because the plugin loads common admin assets for all users in the backend. - Extract Nonce: Use
browser_evalto 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-scriptdata associated with "tribe" prefixes.
- Possible Variable 1:
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¬ice_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
- Plugin Version: Ensure "The Events Calendar" version 6.15.12.2 is installed.
- Subscriber User: Create a user with the
subscriberrole. - 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 evalto force one into the queue:wp eval "Tribe__Common__Admin__Notices::instance()->register_notice( 'test-admin-notice', ['content' => 'Maliciously Dismissible', 'dismiss' => true] );"
- If no notice is present, use
7. Expected Results
- The AJAX request returns
200 OKwith{"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
- Check Options Table: Verify that the notice ID has been added to the dismissed notices option.
wp option get tribe_dismissed_notices - Verify UI: Log in as an Administrator and check if the notice
test-admin-noticeis 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.
- Nonce Location:
- Migration Actions: Check for
actionstrings containingmigrateinsrc/Tribe/Main.php. - Script localization check: If
browser_evalfails to find the nonce, grep the plugin folder forwp_localize_scriptto find the exact object name:grep -r "wp_localize_script" /var/www/html/wp-content/plugins/the-events-calendar/ -A 5
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
@@ -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.