CVE-2026-9838

ICS Calendar <= 12.0.9 - Reflected Cross-Site Scripting via 'htmltagtitle' Parameter

mediumImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
6.1
CVSS Score
6.1
CVSS Score
medium
Severity
12.0.9.2
Patched in
1d
Time to patch

Description

The ICS Calendar plugin for WordPress is vulnerable to Reflected Cross-Site Scripting via the 'htmltagtitle' parameter in all versions up to, and including, 12.0.9 due to insufficient input sanitization and output escaping. This makes it possible for unauthenticated attackers to inject arbitrary web scripts in pages that execute if they can successfully trick a user into performing an action such as clicking on a link. The vulnerability is reachable via the unauthenticated wp_ajax_nopriv_r34ics_ajax AJAX action, which accepts attacker-controlled js_args values merged over stored shortcode configuration without nonce verification, allowing the htmltagtitle key to bypass the normal shortcode allowlist check.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
Required
Scope
Changed
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=12.0.9
PublishedJuly 9, 2026
Last updatedJuly 10, 2026
Affected pluginics-calendar

What Changed in the Fix

Changes introduced in v12.0.9.2

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan outlines the steps for a Proof-of-Concept (PoC) exploit for **CVE-2026-9838**, a reflected Cross-Site Scripting (XSS) vulnerability in the ICS Calendar plugin for WordPress. ## 1. Vulnerability Summary The **ICS Calendar** plugin (up to version 12.0.9) is vulnerable to unauthenti…

Show full research plan

This research plan outlines the steps for a Proof-of-Concept (PoC) exploit for CVE-2026-9838, a reflected Cross-Site Scripting (XSS) vulnerability in the ICS Calendar plugin for WordPress.

1. Vulnerability Summary

The ICS Calendar plugin (up to version 12.0.9) is vulnerable to unauthenticated Reflected XSS. The vulnerability exists because the unauthenticated AJAX action wp_ajax_nopriv_r34ics_ajax (located in r34ics-ajax.php) accepts a js_args parameter containing configuration overrides. These overrides are merged into the calendar's rendering configuration without sufficient sanitization or nonce verification. Specifically, the htmltagtitle parameter—intended to specify the HTML tag used for calendar titles (e.g., h2, h3)—can be manipulated to inject arbitrary HTML tags and JavaScript event handlers.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: r34ics_ajax (Unauthenticated via wp_ajax_nopriv_r34ics_ajax)
  • Vulnerable Parameter: js_args[htmltagtitle]
  • Authentication Required: None (Unauthenticated).
  • Preconditions: The plugin must be active. While the description mentions merging over "stored shortcode configuration," reflected XSS typically triggers even if a specific stored configuration is not found, as the AJAX handler attempts to render a calendar view based on the provided parameters.

3. Code Flow

  1. Entry Point: The request hits admin-ajax.php with action=r34ics_ajax.
  2. AJAX Handler: wp_ajax_nopriv_r34ics_ajax is triggered. The handler (in r34ics-ajax.php) retrieves the js_args array from the request.
  3. Configuration Merge: The plugin merges js_args into the default configuration array ($shortcode_defaults in class-r34ics.php).
  4. Bypass: The AJAX path fails to apply the allowlist validation normally used when processing standard WordPress shortcodes.
  5. Sink: The merged configuration is passed to the rendering logic. When generating the calendar HTML, the value of htmltagtitle is used as a literal tag name in a string concatenation or template (e.g., <{$htmltagtitle}>... </{$htmltagtitle}>).
  6. Reflection: The injected tag (e.g., img src=x onerror=alert(1)) is echoed into the HTTP response.

4. Nonce Acquisition Strategy

According to the vulnerability description, this specific AJAX action does not require nonce verification for the unauthenticated nopriv hook.

  • Nonce Check: Absent or bypassed for wp_ajax_nopriv_r34ics_ajax.
  • Verification: If the exploit fails with a 403 Forbidden or 0 response, the PoC agent should check if a nonce is enqueued on pages containing the [ics_calendar] shortcode.
  • JavaScript Variable (if needed): window.r34ics_ajax?.nonce (inferred).

5. Exploitation Strategy

The goal is to trigger an alert box in a browser context by sending a malicious htmltagtitle value via the AJAX endpoint.

Step 1: Construct the Payload

We will use an <img> tag with an onerror handler to trigger JavaScript execution.

  • Payload: img src=x onerror=alert(document.domain)
  • Request Parameter: js_args[htmltagtitle]=img+src%3Dx+onerror%3Dalert(document.domain)

Step 2: Dispatch the Request

Using the http_request tool, send a POST request to the AJAX endpoint.

  • Method: POST
  • URL: http://<target-ip>/wp-admin/admin-ajax.php
  • Headers:
    • Content-Type: application/x-www-form-urlencoded
  • Body:
    action=r34ics_ajax&js_args[htmltagtitle]=img+src%3Dx+onerror%3Dalert(document.domain)&view=list

Note: Adding view=list or url=https://example.com/feed.ics (a placeholder) may be necessary to ensure the rendering engine triggers the title output.

6. Test Data Setup

  1. Install Plugin: Ensure ICS Calendar <= 12.0.9 is installed and activated.
  2. Create Feed (Optional): To ensure the rendering logic completes, it may help to have a dummy page with the shortcode.
    wp post create --post_type=page --post_title="Calendar Test" --post_status=publish --post_content='[ics_calendar url="https://icscalendar.com/preview/sample.ics"]'
    

7. Expected Results

  • HTTP Response: Status 200 OK.
  • Response Body: Should contain the injected payload verbatim:
    <img src=x onerror=alert(document.domain)>...</img>
    
  • Browser Execution: If viewed in a browser, an alert box showing the document domain will appear.

8. Verification Steps

  1. Manual Verification: Use http_request to fetch the URL and check for the payload in the body.
  2. Automated Check:
    # Use grep to find the reflected payload in the response
    curl -s -X POST http://localhost:8080/wp-admin/admin-ajax.php \
      -d "action=r34ics_ajax" \
      -d "js_args[htmltagtitle]=vulnerable_tag" | grep "vulnerable_tag"
    

9. Alternative Approaches

If the htmltagtitle parameter is specifically blocked or the <img> tag fails, try other htmltag* parameters defined in class-r34ics.php:

  • js_args[htmltagdate]
  • js_args[htmltageventtitle]
  • js_args[htmltagmonth]

Payload variation for attribute breakout:
If the tag name is used inside an existing tag (e.g., <div class="<?php echo $htmltagtitle; ?>">), use:

  • "> <script>alert(1)</script>
Research Findings
Static analysis — not yet PoC-verified

Summary

The ICS Calendar plugin for WordPress is vulnerable to unauthenticated Reflected Cross-Site Scripting via the 'htmltagtitle' and other 'htmltag*' parameters. This occurs because the plugin's AJAX handler accepts configuration overrides through the 'js_args' parameter and merges them into the calendar rendering process without sufficient sanitization, allowing attackers to inject arbitrary HTML and JavaScript.

Vulnerable Code

// class-r34ics.php - Configuration defaults that are merged with user-supplied js_args
// Lines 111-116 in version 12.0.9
			'htmltagdate' => '',
			'htmltageventdesc' => '',
			'htmltageventtitle' => '',
			'htmltagmonth' => '',
			'htmltagtime' => '',
			'htmltagtitle' => '',

---

// The vulnerability resides in the AJAX handler (r34ics-ajax.php) which accepts 'js_args'
// and merges them into the rendering engine's arguments without the allowlist
// validation applied to standard shortcode attributes.
// The values are used as literal HTML tags in the output:
// <{$args['htmltagtitle']}> ... </{$args['htmltagtitle']}>

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/ics-calendar/12.0.9/changelog.txt /home/deploy/wp-safety.org/data/plugin-versions/ics-calendar/12.0.9.2/changelog.txt
--- /home/deploy/wp-safety.org/data/plugin-versions/ics-calendar/12.0.9/changelog.txt	2026-06-02 16:06:10.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/ics-calendar/12.0.9.2/changelog.txt	2026-06-07 14:19:50.000000000 +0000
@@ -1,5 +1,15 @@
 === ICS Calendar Changelog ===
 
+= 12.0.9.2 - 2026.06.07 =
+
+* Changed `id` attribute on AJAX container (appended `-ajax`) so it isn't redundant with the `id` of the calendar itself after loading.
+
+Note: Due the fact that its single change would serve no useful purpose on its own, version 12.0.9.1 was not released in the public WordPress repository, but was included in the ICS Calendar Pro 6.1.10 update.
+
+= 12.0.9.1 - 2026.06.02 =
+
+* Removed auto-redirect to settings page on activation.
+
 = 12.0.9 - 2026.06.02 =
 
 * Security:
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/ics-calendar/12.0.9/class-r34ics.php /home/deploy/wp-safety.org/data/plugin-versions/ics-calendar/12.0.9.2/class-r34ics.php
--- /home/deploy/wp-safety.org/data/plugin-versions/ics-calendar/12.0.9/class-r34ics.php	2026-06-02 16:06:10.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/ics-calendar/12.0.9.2/class-r34ics.php	2026-06-07 14:19:50.000000000 +0000
@@ -2646,7 +2646,7 @@
 				'data-view-is-list-long' => $is_list_long,
 				'data-view-is-list-style' => $is_list_style,
 				'data-view' => $args['view'] ?: '',
-				'id' => $args['guid'] ?: '',
+				'id' => ($args['guid'] ?: r34ics_uid()) . '-ajax',
 			);
 		}
 
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/ics-calendar/12.0.9/ics-calendar.php /home/deploy/wp-safety.org/data/plugin-versions/ics-calendar/12.0.9.2/ics-calendar.php
--- /home/deploy/wp-safety.org/data/plugin-versions/ics-calendar/12.0.9/ics-calendar.php	2026-06-02 16:06:10.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/ics-calendar/12.0.9.2/ics-calendar.php	2026-06-07 14:19:50.000000000 +0000
@@ -3,7 +3,7 @@
 Plugin Name: ICS Calendar
 Plugin URI: https://icscalendar.com
 Description: Turn your Google Calendar, Microsoft Office 365 or Apple iCloud Calendar into a seamlessly integrated, auto-updating, zero-maintenance WordPress experience.
-Version: 12.0.9
+Version: 12.0.9.2
 Requires at least: 5.3
 Requires PHP: 7.4
 Author: Room 34 Creative Services, LLC
@@ -120,6 +120,7 @@
 	register_activation_hook(__FILE__, 'r34ics_install');
 	
 	// Redirect to Getting Started page with first run message
+	/*
 	add_action('admin_init', function() {
 		if (get_option('r34ics_activation_redirect')) {
 			// DO NOT REMOVE THIS LINE! You'll have to manually delete the plugin to stop the redirect loops
@@ -128,6 +129,7 @@
 			exit;
 		}
 	}, PHP_INT_MAX - 1);
+	*/
 	
 	
 	// Updates
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/ics-calendar/12.0.9/readme.txt /home/deploy/wp-safety.org/data/plugin-versions/ics-calendar/12.0.9.2/readme.txt
--- /home/deploy/wp-safety.org/data/plugin-versions/ics-calendar/12.0.9/readme.txt	2026-06-02 16:06:10.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/ics-calendar/12.0.9.2/readme.txt	2026-06-07 14:19:50.000000000 +0000
@@ -5,7 +5,7 @@
 Requires at least: 5.3
 Tested up to: 7.0
 Requires PHP: 7.4
-Stable tag: 12.0.9
+Stable tag: 12.0.9.2
 License: GPLv2 or later
 License URI: http://www.gnu.org/licenses/gpl-2.0.html
 
@@ -101,6 +101,16 @@
 
 == Changelog ==
 
+= 12.0.9.2 - 2026.06.07 =
+
+* Changed `id` attribute on AJAX container (appended `-ajax`) so it isn't redundant with the `id` of the calendar itself after loading.
+
+Note: Due the fact that its single change would serve no useful purpose on its own, version 12.0.9.1 was not released in the public WordPress repository, but was included in the ICS Calendar Pro 6.1.10 update.
+
+= 12.0.9.1 - 2026.06.02 =
+
+* Removed auto-redirect to settings page on activation.
+
 = 12.0.9 - 2026.06.02 =
 
 * Security:

Exploit Outline

An attacker can exploit this by sending a crafted POST request to the WordPress AJAX endpoint (/wp-admin/admin-ajax.php) using the 'r34ics_ajax' action. The payload involves passing an XSS vector (e.g., an <img> tag with an onerror event) via the 'js_args[htmltagtitle]' parameter. Because the AJAX handler does not require a nonce for unauthenticated requests and fails to validate that the provided tag name is a safe HTML element, the injected script is reflected in the AJAX response and executed when the response is rendered in the victim's browser.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.