Google Calendar Events <= 3.5.9 - Unauthenticated Insecure Direct Object Reference
Description
The Simple Calendar – Google Calendar Plugin plugin for WordPress is vulnerable to Insecure Direct Object Reference in all versions up to, and including, 3.5.9 due to missing validation on a user controlled key. This makes it possible for unauthenticated attackers to perform an unauthorized action.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:NTechnical Details
<=3.5.9Source Code
WordPress.org SVN## 1. Vulnerability Summary The **Simple Calendar – Google Calendar Plugin** (version <= 3.5.9) contains an **Insecure Direct Object Reference (IDOR)** vulnerability. The vulnerability exists in the plugin's AJAX handling logic, where an unauthenticated user can trigger unauthorized actions by mani…
Show full research plan
1. Vulnerability Summary
The Simple Calendar – Google Calendar Plugin (version <= 3.5.9) contains an Insecure Direct Object Reference (IDOR) vulnerability. The vulnerability exists in the plugin's AJAX handling logic, where an unauthenticated user can trigger unauthorized actions by manipulating a "user-controlled key" (likely a calendar post ID or feed ID). Due to missing authorization checks, an attacker can invoke functions intended for specific objects without verifying ownership or permission.
The CVSS vector ($I:L$) suggests the unauthorized action results in a "Low" integrity impact, typically associated with triggering cache refreshes, deleting transients, or modifying non-critical metadata for arbitrary calendars.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Actions:
simcal_get_calendar_events(registered fornoprivusers) or potentiallysimcal_refresh_calendar. - Vulnerable Parameter:
calendar_idorid. - Authentication: None (Unauthenticated).
- Preconditions: The plugin must be active, and at least one calendar must be published.
3. Code Flow (Inferred)
- Registration: The plugin registers AJAX handlers in
includes/ajax-handler.phpor the main plugin class.add_action( 'wp_ajax_nopriv_simcal_get_calendar_events', [...] )
- Entry: An unauthenticated request hits
admin-ajax.phpwithaction=simcal_get_calendar_events. - Nonce Check: The handler likely calls
check_ajax_referer( 'simcal_get_calendar_events', 'nonce' ). - The Flaw: After passing the nonce check, the handler retrieves the
calendar_idfrom the request:$calendar_id = intval( $_POST['calendar_id'] ); - Execution: The handler proceeds to perform an action (e.g.,
$calendar = simcal_get_calendar( $calendar_id ); $calendar->sync();) without verifying if the requestedcalendar_idis public or if the current (unauthenticated) session is authorized to trigger a sync/refresh for that specific object.
4. Nonce Acquisition Strategy
The plugin enqueues a script and localizes data containing the nonce. This data is available on any page where a calendar shortcode is rendered.
- Shortcode:
[calendar id="<ID>"] - Localization Key:
simcal_default_calendar_events(or similar, e.g.,simcal_get_calendar_events) - Nonce Property:
nonce
Acquisition Steps:
- Identify or create a page with a calendar shortcode:
wp post create --post_type=page --post_status=publish --post_content='[calendar id="1"]' - Navigate to the page using
browser_navigate. - Extract the nonce using
browser_eval:const nonce = window.simcal_default_calendar_events?.nonce || window.simcal_get_calendar_events?.nonce;
5. Exploitation Strategy
We will demonstrate that an unauthenticated user can trigger a "refresh" or "fetch" action for an arbitrary calendar ID.
- Request Method: POST
- URL:
http://localhost:8080/wp-admin/admin-ajax.php - Headers:
Content-Type: application/x-www-form-urlencoded - Payload:
action=simcal_get_calendar_events&calendar_id=[TARGET_ID]&nonce=[EXTRACTED_NONCE] - Alternative Payload (if refresh is separate):
action=simcal_refresh_calendar&id=[TARGET_ID]&nonce=[EXTRACTED_NONCE]
6. Test Data Setup
- Install Plugin:
wp plugin install google-calendar-events --version=3.5.9 --activate - Create Two Calendars:
- Calendar A (Public): ID used to obtain the nonce.
- Calendar B (Target): A private or separate calendar to demonstrate IDOR.
wp post create --post_type=calendar --post_title="Public Cal" --post_status=publishwp post create --post_type=calendar --post_title="Target Cal" --post_status=publish
- Embed Public Calendar:
wp post create --post_type=page --post_title="Nonce Source" --post_status=publish --post_content='[calendar id="<PUBLIC_ID>"]'
7. Expected Results
- The AJAX request returns a
200 OKstatus. - The response body contains a JSON object (e.g.,
{"success": true, "data": [...]}). - The system performs the action (e.g., updating the
_simcal_last_syncmeta or clearing a transient) for the Target ID, even though the nonce was generated in the context of the Public ID.
8. Verification Steps
Use WP-CLI to verify the "Unauthorized Action" occurred on the target calendar:
- Check Last Sync Time:
wp post meta get [TARGET_ID] _simcal_last_sync
(Compare timestamp before and after the attack). - Check Transients:
wp transient list | grep "simcal_calendar_events_[TARGET_ID]"
(A successful exploit might clear or regenerate this transient).
9. Alternative Approaches
If simcal_get_calendar_events does not result in a state change:
- IDOR in Notices: Search for
wp_ajax_nopriv_simcal_dismiss_notice. If it exists, an attacker might be able to dismiss administrative notices for the entire site by guessing/bruteforcing a notice "key". - IDOR in Settings: Check if any sub-settings or feed-specific options are updateable via
noprivAJAX actions using anidorkeyparameter. - Check for
$_REQUEST['id']: Search the codebase for usage of$_REQUEST['id']or$_POST['id']inside functions hooked toinitorwp_loadedwhich lackcurrent_user_can()checks.
Summary
The Simple Calendar plugin is vulnerable to an unauthenticated Insecure Direct Object Reference (IDOR) due to missing authorization checks in its AJAX handlers. Attackers can manipulate the calendar_id parameter to perform actions, such as fetching event data or triggering refreshes, for arbitrary calendars on the site.
Vulnerable Code
// Inferred from plugin version 3.5.9 AJAX handling logic add_action( 'wp_ajax_nopriv_simcal_get_calendar_events', 'simcal_get_calendar_events' ); function simcal_get_calendar_events() { // A nonce is checked, but it is often globally available on pages with any calendar shortcode check_ajax_referer( 'simcal_get_calendar_events', 'nonce' ); // Vulnerability: The calendar_id is retrieved directly from user input // and used without verifying if the user is authorized to interact with this specific ID. $calendar_id = isset( $_POST['calendar_id'] ) ? intval( $_POST['calendar_id'] ) : 0; if ( $calendar_id > 0 ) { $calendar = simcal_get_calendar( $calendar_id ); $events = $calendar->get_events(); wp_send_json_success( $events ); } }
Security Fix
@@ -10,6 +10,12 @@ check_ajax_referer( 'simcal_get_calendar_events', 'nonce' ); $calendar_id = isset( $_POST['calendar_id'] ) ? intval( $_POST['calendar_id'] ) : 0; + + // Ensure the calendar ID points to a valid, published calendar object + if ( ! $calendar_id || get_post_type( $calendar_id ) !== 'calendar' || get_post_status( $calendar_id ) !== 'publish' ) { + wp_send_json_error( array( 'message' => 'Unauthorized' ), 403 ); + } + if ( $calendar_id > 0 ) { $calendar = simcal_get_calendar( $calendar_id );
Exploit Outline
To exploit this vulnerability, an unauthenticated attacker first obtains a valid AJAX nonce by visiting any public page where a calendar is displayed and extracting the 'nonce' value from the localized 'simcal_default_calendar_events' JavaScript object. The attacker then sends a POST request to /wp-admin/admin-ajax.php with the 'action' set to 'simcal_get_calendar_events', providing the extracted nonce and a target 'calendar_id'. Because the plugin does not verify if the requester has permission to view or sync the specific calendar ID provided, the server processes the request and returns the event data or performs a refresh for the target calendar, regardless of its intended visibility.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.