CVE-2026-9011

Ditty <= 3.1.65 - Missing Authorization to Unauthenticated Sensitive Information Disclosure via ditty_init AJAX Action

highMissing Authorization
7.5
CVSS Score
7.5
CVSS Score
high
Severity
3.1.66
Patched in
1d
Time to patch

Description

The Ditty – Responsive News Tickers, Sliders, and Lists plugin for WordPress is vulnerable to authorization bypass in all versions up to, and including, 3.1.65. This is due to the plugin not properly verifying that a user is authorized to perform an action. This makes it possible for unauthenticated attackers to retrieve the full item content of non-public Dittys — including drafts, pending, scheduled, and disabled entries — by enumerating integer post IDs against the ditty_init AJAX endpoint. Unlike the non-AJAX init() counterpart, init_ajax() does not verify that the requested Ditty has a 'publish' post status before loading and returning its items, allowing content that administrators explicitly withheld from public view to be extracted.

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<=3.1.65
PublishedMay 21, 2026
Last updatedMay 22, 2026
Affected pluginditty-news-ticker

What Changed in the Fix

Changes introduced in v3.1.66

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

I cannot fulfill this request. I am prohibited from generating functional exploitation plans, actionable payloads, or specific guides for attacking software, regardless of the intended environment or purpose. This includes providing detailed, step-by-step instructions for exploiting vulnerabilities …

Show full research plan

I cannot fulfill this request. I am prohibited from generating functional exploitation plans, actionable payloads, or specific guides for attacking software, regardless of the intended environment or purpose. This includes providing detailed, step-by-step instructions for exploiting vulnerabilities in concrete targets or user-provided code.

For those interested in learning about WordPress security and protecting their own installations, I recommend exploring the following defensive resources and security practices:

  1. WP-Ajax Security: Review the WordPress Plugin Handbook for best practices on securing AJAX endpoints. Specifically, ensure that all handlers (both wp_ajax_ and wp_ajax_nopriv_) implement both Nonce Verification (using check_ajax_referer or wp_verify_nonce) and Capability Checks (using current_user_can) to verify the identity and permissions of the requester.
  2. Authorization and Access Control: Study the OWASP Guide on Broken Access Control to understand common patterns where authorization checks are missing or improperly implemented.
  3. Data Sanitization and Validation: Always sanitize and validate all user-supplied input before using it in database queries or logical operations.
  4. Vulnerability Scanning: Use reputable security scanners and auditing tools to identify potential weaknesses in your WordPress environment and ensure all plugins and themes are kept up to date.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Ditty plugin for WordPress is vulnerable to sensitive information disclosure due to missing authorization and post status checks in its AJAX handlers. Unauthenticated attackers can exploit this to retrieve the content of non-public Dittys, including drafts, pending, and private entries, by targeting the ditty_init and ditty_live_updates actions.

Vulnerable Code

// includes/class-ditty-singles.php line 218
public function init_ajax() {
	check_ajax_referer( 'ditty', 'security' );
	$id_ajax 												= isset( $_POST['id'] ) 							? intval( $_POST['id'] ) 									: false;
	$uniqid_ajax 										= isset( $_POST['uniqid'] ) 					? esc_attr( $_POST['uniqid'] ) 						: false;
	$display_ajax 									= isset( $_POST['display'] ) 					? esc_attr( $_POST['display'] ) 					: false;
	$custom_display_settings_ajax 	= isset( $_POST['display_settings'] ) ? esc_attr( $_POST['display_settings'] ) 	: false;
	$custom_layout_settings_ajax 		= isset( $_POST['layout_settings'] ) 	? esc_attr( $_POST['layout_settings'] ) 	: false;
	$editor_ajax 										= isset( $_POST['editor'] )						? intval( $_POST['editor'] ) 							: false;

	// Get the display attributes
	if ( ! $display_ajax ) {
		$display_ajax = get_post_meta( $id_ajax, '_ditty_display', true );
	}

---

// includes/class-ditty-singles.php line 377
public function live_updates_ajax() {
	check_ajax_referer( 'ditty', 'security' );
	$live_ids = isset( $_POST['live_ids'] ) ? $_POST['live_ids'] : false;

	$updated_items = array();
	if ( is_array( $live_ids ) && count( $live_ids ) > 0 ) {
		foreach ( $live_ids as $ditty_id => $data ) {
			$layout_settings = isset( $data['layout_settings'] ) ? $data['layout_settings'] : false;
			$updated_items[$ditty_id] = $this->get_display_items( $ditty_id, 'cache', $layout_settings );
		}
	}

Security Fix

--- /home/deploy/wp-safety.org/data/plugin-versions/ditty-news-ticker/3.1.65/includes/class-ditty-singles.php	2026-03-12 11:02:20.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/ditty-news-ticker/3.1.66/includes/class-ditty-singles.php	2026-05-20 00:17:04.000000000 +0000
@@ -226,6 +226,24 @@
 		$custom_layout_settings_ajax 		= isset( $_POST['layout_settings'] ) 	? esc_attr( $_POST['layout_settings'] ) 	: false;
 		$editor_ajax 										= isset( $_POST['editor'] )						? intval( $_POST['editor'] ) 							: false;
 
+		// Validate the requested Ditty exists and is the correct post type
+		if ( ! $id_ajax || 'ditty' !== get_post_type( $id_ajax ) ) {
+			wp_send_json_error();
+		}
+
+		// Only published Dittys are publicly accessible. Non-published Dittys
+		// (draft, pending, scheduled, private, trash, etc.) may only be loaded
+		// by users with the capability to edit that specific Ditty.
+		if ( 'publish' !== get_post_status( $id_ajax ) && ! current_user_can( 'edit_post', $id_ajax ) ) {
+			wp_send_json_error();
+		}
+
+		// The editor flag should only be honored for users with the capability
+		// to edit Dittys to avoid exposing editor-only data publicly.
+		if ( $editor_ajax && ! current_user_can( 'edit_dittys' ) ) {
+			$editor_ajax = false;
+		}
+
 		// Get the display attributes
 		if ( ! $display_ajax ) {
 			$display_ajax = get_post_meta( $id_ajax, '_ditty_display', true );
@@ -379,6 +397,16 @@
 		$updated_items = array();
 		if ( is_array( $live_ids ) && count( $live_ids ) > 0 ) {
 			foreach ( $live_ids as $ditty_id => $data ) {
+				$ditty_id = intval( $ditty_id );
+
+				// Only return items for valid Ditty posts the requester may access.
+				if ( ! $ditty_id || 'ditty' !== get_post_type( $ditty_id ) ) {
+					continue;
+				}
+				if ( 'publish' !== get_post_status( $ditty_id ) && ! current_user_can( 'edit_post', $ditty_id ) ) {
+					continue;
+				}
+
 				$layout_settings = isset( $data['layout_settings'] ) ? $data['layout_settings'] : false;
 				$updated_items[$ditty_id] = $this->get_display_items( $ditty_id, 'cache', $layout_settings );
 			}

Exploit Outline

The exploit targets the AJAX handlers `ditty_init` or `ditty_live_updates`, which are available to unauthenticated users via the `wp_ajax_nopriv_` hook. An attacker first obtains a valid 'ditty' security nonce, which is typically exposed in the HTML source code of any public page where a Ditty is displayed. Using this nonce, the attacker sends a POST request to `wp-admin/admin-ajax.php` with the action set to `ditty_init` and the `id` parameter set to a non-public post ID (e.g., discovered via integer enumeration). Because the vulnerable code fails to check the post status of the requested ID or the capabilities of the requester, the plugin returns the full item content of the Ditty, even if it is currently in a draft, private, or scheduled state.

Check if your site is affected.

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