Business Directory Plugin <= 6.4.21 - Unauthenticated SQL Injection via payment Parameter
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:NTechnical Details
<=6.4.21Source Code
WordPress.org SVNThis 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
paymentparameter). - Cause: The plugin fails to use
$wpdb->prepare()or adequate escaping when querying the database for a payment object based on thepaymentparameter provided in the request. - Sink: A raw SQL query (e.g.,
$wpdb->get_rowor$wpdb->get_var) involving thepaymentstring.
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$_GETor$_REQUEST). - Action/View: Likely
wpbdp_view=payment_receipt,wpbdp_view=checkout, or handled globally duringinitif thepaymentparameter 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_paymentstable for the query to be reached, though time-based logic can often bypass this depending on the exact query structure.
3. Code Flow (Inferred)
- Entry: A request is made to
/?payment=[PAYLOAD]. - Hook: The plugin's
WPBDP_Payments_APIor a similar controller (registered oninitortemplate_redirect) detects thepaymentparameter. - Processing: The code attempts to load a payment object:
$payment = WPBDP_Payment::get( $_REQUEST['payment'] ); - 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}'" );
Ifpayment_idis 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:
- The plugin uses the
[businessdirectory]shortcode. - Setup:
wp post create --post_type=page --post_status=publish --post_title="Directory" --post_content='[businessdirectory]' - Extraction: Navigate to the new page and check for localized scripts.
- Variable: Look for
window.wpbdp?.ajax_nonceor similar in the page source viabrowser_eval.
- The plugin uses the
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 triggerSLEEP(5).- URL:
http://localhost:8080/ - Method:
GET - Params:
?payment=1' AND (SELECT 1 FROM (SELECT(SLEEP(5)))a) AND '1'='1
- URL:
- 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 forwordpress).- Payload:
1' AND (SELECT 1 FROM (SELECT(IF(SUBSTR(DATABASE(),1,1)='w',SLEEP(5),0)))a) AND '1'='1
- Payload:
6. Test Data Setup
To ensure the code path is hit, we may need to ensure the Business Directory database tables are initialized.
- Activate Plugin: Ensure
business-directory-pluginis active. - 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]' - Verify Table: Confirm the payments table exists:
wp db query "DESCRIBE wp_wpbdp_payments;"(The prefixwp_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
paymentparameter 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.
- Run the exploit script using the
http_requesttool. - Log the
elapsed_time. - If
elapsed_time > 5.0, mark as VULNERABLE. - 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]
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
@@ -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.