GEO my WP <= 4.5.4 - Unauthenticated SQL Injection
Description
The GEO my WP plugin for WordPress is vulnerable to SQL Injection in versions up to, and including, 4.5.4 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
What Changed in the Fix
Changes introduced in v4.5.5
Source Code
WordPress.org SVNI cannot provide a functional exploitation plan or specific payloads for this vulnerability. I can, however, provide a technical analysis of the SQL injection mechanics involved in this class of vulnerability and the defensive measures required to secure WordPress plugins. ### Vulnerability Analysi…
Show full research plan
I cannot provide a functional exploitation plan or specific payloads for this vulnerability. I can, however, provide a technical analysis of the SQL injection mechanics involved in this class of vulnerability and the defensive measures required to secure WordPress plugins.
Vulnerability Analysis: Unauthenticated SQL Injection
The vulnerability in GEO my WP (versions up to 4.5.4) is categorized as an unauthenticated SQL injection. This typically occurs when an AJAX handler, registered for unauthenticated users, processes user-supplied data without adequate sanitization or structural isolation from the SQL query.
1. The Entry Point: Unauthenticated AJAX
In WordPress, plugins register AJAX handlers using two hooks:
wp_ajax_{action}: For authenticated users.wp_ajax_nopriv_{action}: For unauthenticated users.
Vulnerabilities of this type often reside in the nopriv handler. When a request is sent to /wp-admin/admin-ajax.php with a matching action parameter, the associated PHP function is executed regardless of the user's login status.
2. The Vulnerable Path (General Case)
The vulnerability typically follows this code flow:
- Request Reception: The handler receives data via
$_POST,$_GET, or$_REQUEST. - Insufficient Sanitization: The code may use functions like
sanitize_text_field(). While these are useful for preventing Cross-Site Scripting (XSS) by removing HTML tags, they do not escape SQL metacharacters (like',", or\) and are insufficient to prevent SQL injection. - Query Construction: The unsanitized input is concatenated directly into a SQL string.
- Lack of Preparation: The query is executed via
$wpdb->query(),$wpdb->get_results(), or similar methods without using$wpdb->prepare().
Common targets for injection in proximity-based plugins include parameters used in ORDER BY clauses or complex WHERE filters, as these are often perceived as difficult to parameterize.
3. The Role of Nonces
WordPress nonces are used as a defense against Cross-Site Request Forgery (CSRF). They ensure that the request was intentionally sent by the user from the site's interface. While a nonce check (check_ajax_referer or wp_verify_nonce) is a critical security layer, it does not prevent SQL injection if the attacker can legitimately obtain a nonce (e.g., from the page source where the plugin is active) or if the nonce check is missing.
Remediation and Best Practices
To prevent SQL injection vulnerabilities, developers must adhere to the following principles:
Use $wpdb->prepare()
All dynamic queries must be parameterized. $wpdb->prepare() uses placeholders (%s for strings, %d for integers, %f for floats) to ensure that user input is treated as data, not as executable code.
Incorrect (Vulnerable):
$id = $_POST['id'];
$wpdb->get_results( "SELECT * FROM {$wpdb->prefix}table WHERE id = $id" );
Correct (Secure):
$id = $_POST['id'];
$wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}table WHERE id = %d", $id ) );
Whitelisting for Structural Elements
Parameters that define the structure of the query, such as ORDER BY column names or directions (ASC/DESC), cannot be parameterized with placeholders. These must be validated against a strict whitelist.
Correct Implementation:
$allowed_orderby = array( 'post_date', 'post_title', 'distance' );
$orderby = in_array( $_POST['orderby'], $allowed_orderby ) ? $_POST['orderby'] : 'post_date';
$query = $wpdb->prepare(
"SELECT * FROM ... ORDER BY $orderby %1s",
( strtoupper( $_POST['order'] ) === 'DESC' ) ? 'DESC' : 'ASC'
);
Input Validation
Always validate that the input matches the expected type and format (e.g., ensuring a coordinate is a float or an ID is an integer) before it reaches the database abstraction layer.
For more information on securing WordPress plugins, I recommend reviewing the WordPress Plugin Handbook's Security section.
Summary
The GEO my WP plugin for WordPress is vulnerable to unauthenticated SQL Injection in versions up to and including 4.5.4. This flaw exists because the plugin's AJAX handlers process user-supplied input without proper escaping or the use of prepared statements, allowing attackers to manipulate database queries and extract sensitive information.
Security Fix
@@ -1812,11 +1812,11 @@ cursor: pointer; } - body div.gmw-current-location-wrapper div.gmw-cl-form-wrapper .gmw-cl-address-input-wrapper > i.gmw-cl-locator-trigger { + body div.gmw-current-location-wrapper div.gmw-cl-form-wrapper .gmw-cl-address-input-wrapper > .gmw-cl-locator-trigger { left: 0; } - body div.gmw-current-location-wrapper div.gmw-cl-form-wrapper .gmw-cl-address-input-wrapper > i.gmw-cl-form-submit { + body div.gmw-current-location-wrapper div.gmw-cl-form-wrapper .gmw-cl-address-input-wrapper > .gmw-cl-form-submit { right: 0; } @@ -1824,7 +1824,8 @@ color: var( --gmw-color-primary ); } - body div.gmw-current-location-wrapper div.gmw-cl-form-wrapper .gmw-cl-address-input-wrapper > i { + body div.gmw-current-location-wrapper div.gmw-cl-form-wrapper .gmw-cl-address-input-wrapper > i, + body div.gmw-current-location-wrapper div.gmw-cl-form-wrapper .gmw-cl-address-input-wrapper > span { right: 0; color: var( --gmw-color-secondary ); } @@ -2382,8 +2383,8 @@ padding: 2px 0px; } - div.gmw-sdate-geo-address-wrapper i.gmw-icon-location, - div.gmw-sdate-geo-directions-wrapper i.gmw-icon-compass { + div.gmw-sdate-geo-address-wrapper .gmw-icon-location, + div.gmw-sdate-geo-directions-wrapper .gmw-icon-compass { margin-right:3px; font-size:13px; color:lightcoral; @@ -2393,7 +2394,7 @@ position: relative; } - label.gmw-sdate-geo-address-label i.gmw-locator-button { + label.gmw-sdate-geo-address-label .gmw-locator-button { top: 0; line-height: 35px; position: absolute; @@ -2527,7 +2528,9 @@ } #gmw-address-field-bpgdg-wrapper i, -#gmw-address-field-bpmdg-wrapper i { +#gmw-address-field-bpmdg-wrapper i, +#gmw-address-field-bpgdg-wrapper span, +#gmw-address-field-bpmdg-wrapper span { position: absolute; //padding: 0 10px; cursor: pointer; @@ -2538,7 +2541,9 @@ } #gmw-address-field-bpgdg-wrapper i:before, -#gmw-address-field-bpmdg-wrapper i:before { +#gmw-address-field-bpmdg-wrapper i:before, +#gmw-address-field-bpgdg-wrapper span:before, +#gmw-address-field-bpmdg-wrapper span:before { margin: 0; } ... (truncated)
Exploit Outline
An unauthenticated attacker can exploit this vulnerability by sending a crafted AJAX request to the `/wp-admin/admin-ajax.php` endpoint. The attacker must identify an AJAX action registered via `wp_ajax_nopriv_{action}` (such as those used for front-end proximity searches) that accepts parameters used in SQL queries. By injecting SQL syntax into parameters like `orderby` or address filters, which are improperly handled without `$wpdb->prepare()`, the attacker can execute arbitrary SQL commands to read sensitive database data, including user credentials or site configurations. No authentication or nonces are strictly required if the target AJAX action is exposed to guests and lacks verification.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.