WP Store Locator <= 2.2.261 - Authenticated (Contributor+) Stored Cross-Site Scripting via 'wpsl_address' Post Meta
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:NTechnical Details
<=2.2.261What Changed in the Fix
Changes introduced in v2.3.0
Source Code
WordPress.org SVN# 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) orwp-admin/post-new.php(for creation). - Vulnerable Hook:
save_postaction registered inWPSL_Metaboxes. - Vulnerable Parameter:
wpsl[address](which maps to thewpsl_addressmeta key). - Authentication Level: Contributor or higher. Contributors can typically create and edit their own
wpsl_storesposts. - Preconditions:
- The
wpsl_storescustom 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.
- The
3. Code Flow
- Entry Point (Admin): An authenticated user with permission to edit
wpsl_storesposts submits a post save request. - Hook Registration:
admin/class-metaboxes.phpregisters the save handler:add_action( 'save_post', array( $this, 'save_post' ) ); - Nonce Verification: The
save_postmethod inWPSL_Metaboxeschecks for a nonce:if ( ! isset( $_POST['wpsl_meta_nonce'] ) || ! wp_verify_nonce( $_POST['wpsl_meta_nonce'], 'save_store_meta' ) ) { return; } - Vulnerable Sink (Storage): The code iterates over the
wpslarray in$_POSTand saves each key/value pair as post meta prefixed withwpsl_:if ( isset( $_POST['wpsl'] ) ) { foreach ( $_POST['wpsl'] as $key => $value ) { update_post_meta( $post_id, 'wpsl_' . $key, $value ); // No sanitization } } - Frontend Sink (Execution): When a user views a page with the
[wpsl]shortcode, the plugin fetches store data (often via thewpsl_storesAJAX action). The store'saddress(fromwpsl_addressmeta) is returned in a JSON response. The frontend JavaScript (typicallyjs/wpsl-gmap.js) uses this address to populate the Google MapsInfoWindowcontent 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.
- Navigate to Editor: Use
browser_navigateto go to the "Add New Store" page:/wp-admin/post-new.php?post_type=wpsl_stores. - Extract Nonce: Use
browser_evalto extract the nonce from the hidden input field:document.querySelector('input[name="wpsl_meta_nonce"]').value - Capture Post ID: The post ID is usually present in the form action or as a hidden input
post_ID.
5. Exploitation Strategy
- Login: Authenticate as a Contributor-level user.
- Initialize Store: Create a draft
wpsl_storespost to obtain a Post ID. - Get Nonce: Navigate to the edit page for that Store and extract the
wpsl_meta_nonce. - Inject Payload: Send a POST request to
/wp-admin/post.phpwith the XSS payload.- URL:
/wp-admin/post.php - Method:
POST - Content-Type:
application/x-www-form-urlencoded - Body Parameters:
action:editpostpost_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)
- URL:
- 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
- User: Create a user with the
contributorrole. - 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
- 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.phpshould return a 302 redirect back to the editor page, indicating a successful update. - The database should contain the XSS payload in the
wp_postmetatable for the specificpost_idunder the keywpsl_address. - Upon clicking the store marker on the frontend map, a JavaScript alert showing the document domain should appear.
8. Verification Steps
- Database Check: Verify the meta value is stored raw:
wp post meta get [POST_ID] wpsl_address - Response Check: Intercept the AJAX call used by the map to fetch stores:
- Action:
wpsl_stores - Verify the
addressfield in the JSON response contains the unescaped<img ...>tag.
- Action:
9. Alternative Approaches
- Rest API: Check if
wp/v2/wpsl_storesendpoints are enabled and allow meta updates via themetafield if registered withshow_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 explicitlatandlng(as shown in Step 5) is the most reliable way to ensure the malicious store is rendered. - Other Fields: The
wpsl[hours],wpsl[phone], andwpsl[url]fields are also likely saved via the same unvalidated loop inadmin/class-metaboxes.phpand should be tested as secondary injection points.
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
@@ -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.