CVE-2026-3361

WP Store Locator <= 2.2.261 - Authenticated (Contributor+) Stored Cross-Site Scripting via 'wpsl_address' Post Meta

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

Description

The WP Store Locator plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'wpsl_address' post meta value in versions up to, and including, 2.2.261 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with contributor-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page and opens an injected map marker info window.

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<=2.2.261
PublishedApril 22, 2026
Last updatedApril 23, 2026
Affected pluginwp-store-locator

What Changed in the Fix

Changes introduced in v2.3.0

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Vulnerability Analysis: CVE-2026-3361 - WP Store Locator Authenticated Stored XSS ## 1. Vulnerability Summary The **WP Store Locator** plugin (versions <= 2.2.261) is vulnerable to **Stored Cross-Site Scripting (XSS)** via the `wpsl_address` post meta. The vulnerability exists because the plugin'…

Show full research plan

Vulnerability Analysis: CVE-2026-3361 - WP Store Locator Authenticated Stored XSS

1. Vulnerability Summary

The WP Store Locator plugin (versions <= 2.2.261) is vulnerable to Stored Cross-Site Scripting (XSS) via the wpsl_address post meta. The vulnerability exists because the plugin's administrative logic for saving "Store" post types iterates through user-provided metadata in the $_POST['wpsl'] array and saves it directly to the database using update_post_meta() without prior sanitization. Furthermore, when these addresses are displayed in the map's info window on the frontend, they are not properly escaped, allowing arbitrary JavaScript execution in the context of any user (including administrators) viewing the store map.

2. Attack Vector Analysis

  • Endpoint: wp-admin/post.php (for updating) or wp-admin/post-new.php (for creation).
  • Vulnerable Hook: save_post action registered in WPSL_Metaboxes.
  • Vulnerable Parameter: wpsl[address] (which maps to the wpsl_address meta key).
  • Authentication Level: Contributor or higher. Contributors can typically create and edit their own wpsl_stores posts.
  • Preconditions:
    • The wpsl_stores custom post type must be available (registered by the plugin).
    • A page containing the [wpsl] shortcode must exist for the payload to be rendered on the frontend.
    • Valid latitude and longitude must be provided (or geocoded) so the store appears on the map.

3. Code Flow

  1. Entry Point (Admin): An authenticated user with permission to edit wpsl_stores posts submits a post save request.
  2. Hook Registration: admin/class-metaboxes.php registers the save handler:
    add_action( 'save_post', array( $this, 'save_post' ) );
    
  3. Nonce Verification: The save_post method in WPSL_Metaboxes checks for a nonce:
    if ( ! isset( $_POST['wpsl_meta_nonce'] ) || ! wp_verify_nonce( $_POST['wpsl_meta_nonce'], 'save_store_meta' ) ) {
        return;
    }
    
  4. Vulnerable Sink (Storage): The code iterates over the wpsl array in $_POST and saves each key/value pair as post meta prefixed with wpsl_:
    if ( isset( $_POST['wpsl'] ) ) {
        foreach ( $_POST['wpsl'] as $key => $value ) {
            update_post_meta( $post_id, 'wpsl_' . $key, $value ); // No sanitization
        }
    }
    
  5. Frontend Sink (Execution): When a user views a page with the [wpsl] shortcode, the plugin fetches store data (often via the wpsl_stores AJAX action). The store's address (from wpsl_address meta) is returned in a JSON response. The frontend JavaScript (typically js/wpsl-gmap.js) uses this address to populate the Google Maps InfoWindow content without escaping, leading to XSS.

4. Nonce Acquisition Strategy

To exploit the save_post logic, the attacker needs a valid wpsl_meta_nonce (action: save_store_meta). This nonce is generated in the "Store Details" metabox on the store editor page.

  1. Navigate to Editor: Use browser_navigate to go to the "Add New Store" page: /wp-admin/post-new.php?post_type=wpsl_stores.
  2. Extract Nonce: Use browser_eval to extract the nonce from the hidden input field:
    document.querySelector('input[name="wpsl_meta_nonce"]').value
    
  3. Capture Post ID: The post ID is usually present in the form action or as a hidden input post_ID.

5. Exploitation Strategy

  1. Login: Authenticate as a Contributor-level user.
  2. Initialize Store: Create a draft wpsl_stores post to obtain a Post ID.
  3. Get Nonce: Navigate to the edit page for that Store and extract the wpsl_meta_nonce.
  4. Inject Payload: Send a POST request to /wp-admin/post.php with the XSS payload.
    • URL: /wp-admin/post.php
    • Method: POST
    • Content-Type: application/x-www-form-urlencoded
    • Body Parameters:
      • action: editpost
      • post_ID: [THE_POST_ID]
      • wpsl_meta_nonce: [EXTRACTED_NONCE]
      • wpsl[address]: <img src=x onerror=alert(document.domain)>
      • wpsl[city]: London (Required field)
      • wpsl[country]: United Kingdom (Required field)
      • wpsl[lat]: 51.5074 (Ensures it appears on the map)
      • wpsl[lng]: -0.1278 (Ensures it appears on the map)
  5. Trigger XSS: Navigate to a public page containing the [wpsl] shortcode, find the marker at the specified lat/lng, and click it to open the InfoWindow.

6. Test Data Setup

  1. User: Create a user with the contributor role.
  2. Settings: Configure WP Store Locator with a default starting point so the map loads.
    • wp option update wpsl_settings '{"start_latlng":"51.5074,-0.1278","api_browser_key":"YOUR_GOOGLE_MAPS_API_KEY"}' --format=json
  3. Shortcode Page: Create a page that displays the store locator.
    • wp post create --post_type=page --post_title="Store Locator" --post_content='[wpsl]' --post_status=publish

7. Expected Results

  • The POST request to post.php should return a 302 redirect back to the editor page, indicating a successful update.
  • The database should contain the XSS payload in the wp_postmeta table for the specific post_id under the key wpsl_address.
  • Upon clicking the store marker on the frontend map, a JavaScript alert showing the document domain should appear.

8. Verification Steps

  1. Database Check: Verify the meta value is stored raw:
    wp post meta get [POST_ID] wpsl_address
    
  2. Response Check: Intercept the AJAX call used by the map to fetch stores:
    • Action: wpsl_stores
    • Verify the address field in the JSON response contains the unescaped <img ...> tag.

9. Alternative Approaches

  • Rest API: Check if wp/v2/wpsl_stores endpoints are enabled and allow meta updates via the meta field if registered with show_in_rest => true.
  • Geocode Bypass: If the geocoding service is active and the attacker does not provide lat/lng, the plugin might attempt to geocode the XSS payload via Google Maps API. This usually fails. Providing explicit lat and lng (as shown in Step 5) is the most reliable way to ensure the malicious store is rendered.
  • Other Fields: The wpsl[hours], wpsl[phone], and wpsl[url] fields are also likely saved via the same unvalidated loop in admin/class-metaboxes.php and should be tested as secondary injection points.
Research Findings
Static analysis — not yet PoC-verified

Summary

The WP Store Locator plugin is vulnerable to Stored Cross-Site Scripting due to a lack of input sanitization and output escaping on store metadata, specifically the 'wpsl_address' field. Authenticated attackers with Contributor-level permissions or higher can inject malicious JavaScript into store details, which then executes when any user, including administrators, interacts with the store marker on a frontend map.

Vulnerable Code

// admin/class-metaboxes.php (inside save_post method)

if ( isset( $_POST['wpsl'] ) ) {
    foreach ( $_POST['wpsl'] as $key => $value ) {
        /* The plugin iterates through the wpsl array and saves meta directly without sanitization */
        update_post_meta( $post_id, 'wpsl_' . $key, $value ); 
    }
}

Security Fix

--- admin/class-metaboxes.php
+++ admin/class-metaboxes.php
@@ -382,7 +382,7 @@
 
         if ( isset( $_POST['wpsl'] ) ) {
             foreach ( $_POST['wpsl'] as $key => $value ) {
-                update_post_meta( $post_id, 'wpsl_' . $key, $value );
+                update_post_meta( $post_id, 'wpsl_' . $key, sanitize_text_field( $value ) );
             }
         }

Exploit Outline

To exploit this vulnerability, an attacker with Contributor-level access must follow these steps: 1. Log in to the WordPress dashboard and navigate to the 'Add New Store' page for the 'wpsl_stores' post type. 2. Extract the 'wpsl_meta_nonce' from the hidden input field in the Store Details metabox. 3. Send a POST request to '/wp-admin/post.php' (or 'post-new.php') with the 'action' set to 'editpost' or 'post-new', and include the XSS payload in the 'wpsl[address]' parameter (e.g., <img src=x onerror=alert(document.domain)>). 4. Ensure the request includes valid latitude ('wpsl[lat]') and longitude ('wpsl[lng]') values to ensure the store appears on the frontend map. 5. Once saved, any user who visits a page containing the [wpsl] shortcode and clicks on the marker for the malicious store will trigger the JavaScript execution via the Google Maps InfoWindow.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.