CVE-2026-4268

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

mediumImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
6.4
CVSS Score
6.4
CVSS Score
medium
Severity
10.0.06
Patched in
1d
Time to patch

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:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Changed
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=10.0.05
PublishedMarch 17, 2026
Last updatedMarch 18, 2026
Affected pluginwp-google-maps

What Changed in the Fix

Changes introduced in v10.0.06

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# 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 the admin_post_wpgmza_save_settings hook)
  • 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

  1. Entry Point: The plugin registers the action in includes/class.settings-page.php:
    add_action('admin_post_wpgmza_save_settings', function() {
        $settingsPage = SettingsPage::createInstance();
    });
    
  2. Initialization: SettingsPage::createInstance() instantiates the SettingsPage class.
  3. Execution Sink: The SettingsPage::__construct() method (in includes/class.settings-page.php) handles the request:
    • It checks if(empty($_POST)). Since we are sending a POST request, it enters the else block.
    • It performs a nonce check: if(!$this->isNonceValid($this->form, $_POST['nonce'])).
    • It iterates through $_POST data and performs sanitization except for wpgmza_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->settings object:
      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.

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:

  1. Login as a Subscriber user.
  2. Navigate to wp-admin/index.php.
  3. Use http_request to send a POST request to wp-admin/admin-ajax.php with:
    • action=heartbeat
    • screen_id=wp-google-maps-settings (to trigger the strpos check)
    • _nonce (the standard WordPress heartbeat nonce, visible in the wp-admin source)
  4. Extract wpgmza_nonce from 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

  1. Login: Authenticate as a Subscriber.
  2. Obtain Nonce:
    • Navigate to /wp-admin/index.php.
    • Execute JS via browser_eval to fetch the heartbeat nonce: wp.heartbeat.post('wpgmza', {screen_id: 'wp-google-maps'}, (response) => { console.log(response.wpgmza_nonce); }).
    • Or manually fetch admin-ajax.php using http_request.
  3. Inject Payload:
    • Send a POST request to /wp-admin/admin-post.php using http_request.
    • Body:
      • action=wpgmza_save_settings
      • nonce=[EXTRACTED_NONCE]
      • wpgmza_custom_js=alert("XSS_EXPLOITED_" + document.domain);
    • Headers: Content-Type: application/x-www-form-urlencoded
  4. Trigger XSS:
    • Navigate to any page containing a map shortcode [wp-google-maps] or the plugin's settings page as an administrator.

6. Test Data Setup

  1. Users: Create a Subscriber user (e.g., attacker/attacker).
  2. Map Content: Ensure at least one map exists.
    • wp google-map create --map_title="Test Map"
  3. 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.php should return a 302 Redirect back to the referrer (indicating success) or a 200 OK without error.
  • The wpgmza_settings option 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

  1. Database Check:
    wp option get wpgmza_settings --format=json
    Check if the wpgmza_custom_js key contains the payload.
  2. Frontend Verification:
    Navigate to the "Map Page" using browser_navigate and 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_nonces is active, any admin-ajax.php request that triggers the heartbeat action with the correct screen_id will leak the nonce required for the settings update.
Research Findings
Static analysis — not yet PoC-verified

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

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wp-google-maps/10.0.05/includes/class.admin-ui.php /home/deploy/wp-safety.org/data/plugin-versions/wp-google-maps/10.0.06/includes/class.admin-ui.php
--- /home/deploy/wp-safety.org/data/plugin-versions/wp-google-maps/10.0.05/includes/class.admin-ui.php	2026-03-17 12:24:24.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wp-google-maps/10.0.06/includes/class.admin-ui.php	2026-04-15 08:36:52.000000000 +0000
@@ -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;
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wp-google-maps/10.0.05/includes/class.settings-page.php /home/deploy/wp-safety.org/data/plugin-versions/wp-google-maps/10.0.06/includes/class.settings-page.php
--- /home/deploy/wp-safety.org/data/plugin-versions/wp-google-maps/10.0.05/includes/class.settings-page.php	2026-03-17 12:24:24.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wp-google-maps/10.0.06/includes/class.settings-page.php	2026-04-15 08:36:52.000000000 +0000
@@ -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.