CVE-2026-23548

DirectoryPress – Business Directory And Classified Ad Listing <= 3.6.25 - Missing Authorization

mediumMissing Authorization
5.3
CVSS Score
5.3
CVSS Score
medium
Severity
3.6.26
Patched in
138d
Time to patch

Description

The DirectoryPress – Business Directory And Classified Ad Listing plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in all versions up to, and including, 3.6.25. 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:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
None
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=3.6.25
PublishedDecember 18, 2025
Last updatedMay 4, 2026
Affected plugindirectorypress

What Changed in the Fix

Changes introduced in v3.6.26

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This plan outlines the research and proof-of-concept exploitation for **CVE-2026-23548**, a Missing Authorization vulnerability in the **DirectoryPress** plugin. ### 1. Vulnerability Summary The DirectoryPress plugin (<= 3.6.25) registers several AJAX handlers that lack proper authorization (capabi…

Show full research plan

This plan outlines the research and proof-of-concept exploitation for CVE-2026-23548, a Missing Authorization vulnerability in the DirectoryPress plugin.

1. Vulnerability Summary

The DirectoryPress plugin (<= 3.6.25) registers several AJAX handlers that lack proper authorization (capability) checks and, in some cases, nonce verification. Specifically, the functions responsible for retrieving and saving category (taxonomy term) configuration metadata are exposed to unauthorized users. An unauthenticated attacker can exploit this to modify directory category settings, such as icons, colors, and background images, which affects the site's front-end presentation and map markers.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: directorypress_save_category_fields_ajax (and potentially directorypress_terms_configuration_html)
  • Vulnerable Function: directorypress_save_category_fields_ajax in includes/core/terms/categories-functions.php.
  • Authentication: Unauthenticated (PR:N). The plugin often registers AJAX actions via wp_ajax_nopriv_ or fails to restrict wp_ajax_ handlers to administrative roles.
  • Preconditions: A directory category (term in the directorypress-category taxonomy) must exist.

3. Code Flow

  1. Entry Point: An unauthenticated request is sent to admin-ajax.php with action=directorypress_save_category_fields_ajax.
  2. Hook Registration: In includes/core/terms/categories-functions.php, the action is registered:
    add_action('wp_ajax_directorypress_save_category_fields_ajax', 'directorypress_save_category_fields_ajax');
    // Note: The vulnerability implies this is either reachable via nopriv or lacks capability checks for existing users.
    
  3. Missing Check: The function directorypress_save_category_fields_ajax (as seen in the truncated directorypress_terms_configuration_html which follows the same pattern) likely proceeds directly to sanitize_text_field($_POST['term_id']) and updates term metadata without calling current_user_can('manage_options') or verifying a nonce.
  4. Sink: The function calls update_term_meta() to store attacker-supplied values in the database.

4. Nonce Acquisition Strategy

While many handlers in this plugin are missing nonces, some might require dp_ajax_nonce. If the target handler requires it, follow this strategy:

  1. Identify Trigger: The plugin enqueues the directorypress-ajax-nonce via wp_localize_script on any page where the DirectoryPress main shortcode is present.
  2. Test Data Setup: Create a public page containing the directory shortcode:
    wp post create --post_type=page --post_status=publish --post_title="Directory" --post_content='[directorypress]'
    
  3. Navigate and Extract:
    • Use browser_navigate to visit the newly created page.
    • Use browser_eval to extract the nonce from the localized JS object:
      // Common object names in DirectoryPress: directorypress_js_obj or dp_js_obj
      window.directorypress_js_obj?.dp_ajax_nonce || window.dp_js_obj?.dp_ajax_nonce
      

5. Exploitation Strategy

We will attempt to modify the color of a specific directory category.

Step 1: Discover Target Category
Retrieve an existing category ID for the directorypress-category taxonomy.

wp term list directorypress-category --format=ids

Step 2: Execute Unauthorized Metadata Update
Send a POST request to update the category's marker color.

  • URL: `http://
Research Findings
Static analysis — not yet PoC-verified

Summary

The DirectoryPress plugin for WordPress is vulnerable to unauthorized modification of directory category metadata due to a missing capability check in the directorypress_save_category_fields_ajax function. This allows unauthenticated attackers to perform administrative actions such as changing category icons, colors, and map marker settings by interacting with the WordPress AJAX endpoint.

Vulnerable Code

// includes/core/terms/categories-functions.php line 16
add_action('wp_ajax_directorypress_save_category_fields_ajax', 'directorypress_save_category_fields_ajax');

// ...

// includes/core/terms/categories-functions.php line 386
function directorypress_save_category_fields_ajax() {
	$response = array();
	$term_id = sanitize_text_field($_POST['term_id']);
	
	if ( isset( $_POST['directorypress_category_icon'] ) ) {
            
		update_term_meta( esc_attr($term_id), 'directorypress_category_icon', $_POST['directorypress_category_icon'] );
	}
	if ( isset( $_POST['directorypress_category_icon_for_listing'] ) ) {
		update_term_meta( esc_attr($term_id), 'directorypress_category_icon_for_listing', $_POST['directorypress_category_icon_for_listing'] );
	}
	if ( isset( $_POST['directorypress_category_icon_for_map'] ) ) {
            
		update_term_meta( esc_attr($term_id), 'directorypress_category_icon_for_map', $_POST['directorypress_category_icon_for_map'] );
	}
	if ( isset( $_POST['category-image-id'] ) ) {
            
			update_term_meta( esc_attr($term_id), 'category-image-id', $_POST['category-image-id'] );
	}
	if ( isset( $_POST['directorypress_category_font_icon'] ) ) {
            $directorypress_category_font_icon = sanitize_text_field($_POST['directorypress_category_font_icon']);
			 update_term_meta( esc_attr($term_id), 'directorypress_category_font_icon', $directorypress_category_font_icon );
	}
	if ( isset( $_POST['marker_color'] ) ) {
            $directorypress_category_color = sanitize_text_field($_POST['marker_color']);
			 update_term_meta(esc_attr($term_id), 'marker_color', $directorypress_category_color );
	}
	
	$response['type'] = 'success';
	$response['message'] = esc_html__('updated successfully', 'DIRECTORYPRESS');
	
	wp_send_json($response); 
	
}

Security Fix

--- /home/deploy/wp-safety.org/data/plugin-versions/directorypress/3.6.25/includes/core/terms/categories-functions.php	2024-12-09 18:34:34.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/directorypress/3.6.26/includes/core/terms/categories-functions.php	2026-01-24 17:19:54.000000000 +0000
@@ -384,35 +384,41 @@
 
 // Save Category Fields
 function directorypress_save_category_fields_ajax() {
-	$response = array();
-	$term_id = sanitize_text_field($_POST['term_id']);
-	
-	if ( isset( $_POST['directorypress_category_icon'] ) ) {
-            
-		update_term_meta( esc_attr($term_id), 'directorypress_category_icon', $_POST['directorypress_category_icon'] );
-	}
-	if ( isset( $_POST['directorypress_category_icon_for_listing'] ) ) {
-		update_term_meta( esc_attr($term_id), 'directorypress_category_icon_for_listing', $_POST['directorypress_category_icon_for_listing'] );
-	}
-	if ( isset( $_POST['directorypress_category_icon_for_map'] ) ) {
-            
-		update_term_meta( esc_attr($term_id), 'directorypress_category_icon_for_map', $_POST['directorypress_category_icon_for_map'] );
-	}
-	if ( isset( $_POST['category-image-id'] ) ) {
-            
-			update_term_meta( esc_attr($term_id), 'category-image-id', $_POST['category-image-id'] );
+	if (current_user_can( 'manage_options' ) ) {
+		
+		$response = array();
+		$term_id = sanitize_text_field($_POST['term_id']);
+		
+		if ( isset( $_POST['directorypress_category_icon'] ) ) {
+				
+			update_term_meta( esc_attr($term_id), 'directorypress_category_icon', $_POST['directorypress_category_icon'] );
+		}
+		if ( isset( $_POST['directorypress_category_icon_for_listing'] ) ) {
+			update_term_meta( esc_attr($term_id), 'directorypress_category_icon_for_listing', $_POST['directorypress_category_icon_for_listing'] );
+		}
+		if ( isset( $_POST['directorypress_category_icon_for_map'] ) ) {
+				
+			update_term_meta( esc_attr($term_id), 'directorypress_category_icon_for_map', $_POST['directorypress_category_icon_for_map'] );
+		}
+		if ( isset( $_POST['category-image-id'] ) ) {
+				
+				update_term_meta( esc_attr($term_id), 'category-image-id', $_POST['category-image-id'] );
+		}
+		if ( isset( $_POST['directorypress_category_font_icon'] ) ) {
+				$directorypress_category_font_icon = sanitize_text_field($_POST['directorypress_category_font_icon']);
+				 update_term_meta( esc_attr($term_id), 'directorypress_category_font_icon', $directorypress_category_font_icon );
+		}
+		if ( isset( $_POST['marker_color'] ) ) {
+				$directorypress_category_color = sanitize_text_field($_POST['marker_color']);
+				 update_term_meta(esc_attr($term_id), 'marker_color', $directorypress_category_color );
+		}
+		
+		$response['type'] = 'success';
+		$response['message'] = esc_html__('updated successfully', 'DIRECTORYPRESS');
+	}else{
+		$response['type'] = 'error';
+		$response = esc_html__('no permission!', 'DIRECTORYPRESS');
 	}
-	if ( isset( $_POST['directorypress_category_font_icon'] ) ) {
-            $directorypress_category_font_icon = sanitize_text_field($_POST['directorypress_category_font_icon']);
-			 update_term_meta( esc_attr($term_id), 'directorypress_category_font_icon', $directorypress_category_font_icon );
-	}
-	if ( isset( $_POST['marker_color'] ) ) {
-            $directorypress_category_color = sanitize_text_field($_POST['marker_color']);
-			 update_term_meta(esc_attr($term_id), 'marker_color', $directorypress_category_color );
-	}
-	
-	$response['type'] = 'success';
-	$response['message'] = esc_html__('updated successfully', 'DIRECTORYPRESS');
 	
 	wp_send_json($response);

Exploit Outline

The exploit targets the AJAX action `directorypress_save_category_fields_ajax` which is registered without any capability or authorization checks. 1. Target Identification: The attacker identifies a valid category term ID within the `directorypress-category` taxonomy (e.g., by inspecting the front-end or using public enumeration). 2. Payload Construction: The attacker prepares a POST request to `/wp-admin/admin-ajax.php` with the following parameters: - `action`: `directorypress_save_category_fields_ajax` - `term_id`: The ID of the category to modify. - Metadata fields: Any supported category fields like `marker_color`, `directorypress_category_font_icon`, or `directorypress_category_icon` with the desired attacker-supplied values. 3. Request Execution: The attacker submits the request. Because the handler lacks `current_user_can()` checks and nonce verification, the plugin executes `update_term_meta()` with the provided values. 4. Verification: The changes can be verified by observing the updated markers or icons in the directory front-end or map views.

Check if your site is affected.

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