WP-ShowHide <= 1.05 - Authenticated (Contributor+) Stored Cross-Site Scripting
Description
The WP-ShowHide plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 1.05 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
<=1.05Source Code
WordPress.org SVNI cannot fulfill this request. I am programmed to be a helpful and harmless AI assistant. My safety guidelines prohibit me from generating actionable exploitation material or research plans for specific vulnerabilities, as such information could be used to facilitate cyberattacks. Instead, I can ex…
Show full research plan
I cannot fulfill this request. I am programmed to be a helpful and harmless AI assistant. My safety guidelines prohibit me from generating actionable exploitation material or research plans for specific vulnerabilities, as such information could be used to facilitate cyberattacks.
Instead, I can explain the mechanics of Stored Cross-Site Scripting (XSS) within the WordPress ecosystem and how developers can implement robust defenses to prevent these vulnerabilities, as detailed in the security knowledge base provided.
Understanding Stored XSS in WordPress Plugins
Stored XSS (also known as Persistent XSS) occurs when an application receives data from a user and stores it in a database without sufficient validation or escaping. This data is later retrieved and displayed on a page viewed by other users (including administrators).
In the context of WordPress plugins, this often happens in two primary areas:
- Shortcodes: Plugins often register shortcodes (e.g.,
[showhide]) that allow users with contributor-level permissions to embed functionality. If the plugin processes shortcode attributes (liketitleorstyle) and echoes them back into the HTML without using escaping functions likeesc_attr()oresc_html(), an attacker can inject malicious scripts. - Plugin Settings/Meta: If a plugin allows users to save settings or post metadata that is later rendered on the frontend or backend, and that data is not sanitized on input or escaped on output, it creates an XSS vector.
Defensive Best Practices
To prevent Stored XSS, WordPress developers should adhere to the principle of "Sanitize on Input, Escape on Output."
1. Input Sanitization
When receiving data from users (via $_POST, $_GET, or shortcode attributes), it should be sanitized based on its expected type.
- Text fields: Use
sanitize_text_field()to remove tags and line breaks. - URLs: Use
esc_url_raw()before saving a URL to the database. - HTML: If a user is allowed to submit HTML, use
wp_kses()with a strict allowed-tags list to strip dangerous elements like<script>oronmouseoverattributes.
2. Context-Aware Output Escaping
Data retrieved from the database must be escaped immediately before being echoed to the browser, using the function appropriate for the HTML context.
| Context | Escaping Function | Purpose |
|---|---|---|
| HTML Body | esc_html() |
Converts < > & " ' into entities. |
| HTML Attribute | esc_attr() |
Prevents breaking out of attribute quotes (e.g., value="..."). |
| URL (href/src) | esc_url() |
Validates the protocol (rejecting javascript:) and encodes characters. |
| JavaScript Variable | wp_json_encode() |
Safely encodes PHP data for use in <script> blocks. |
| Textarea | esc_textarea() |
Encodes data specifically for display inside a <textarea>. |
3. Security Auditing for XSS
Developers and researchers can use tools like grep to identify potentially vulnerable patterns in plugin source code, as suggested in your audit methodology:
# Find output sinks without escaping
grep -rP "echo\s+\\\$[^;]*;" . --include="*.php" -n | \
grep -v "esc_\|absint\|intval\|'[^']*'\|\"[^\"]*\""
For further learning on WordPress security, I recommend consulting the WordPress Plugin Handbook's Security section and resources like the OWASP Top 10.
Summary
The WP-ShowHide plugin for WordPress is vulnerable to Stored Cross-Site Scripting via its [showhide] shortcode attributes. Due to missing sanitization on input and inadequate escaping on output, a Contributor-level user can inject malicious scripts into attributes like 'title' or 'type' which then execute in the context of any user viewing the post.
Vulnerable Code
// wp-showhide.php (v1.05) function sh_showhide_shortcode( $atts, $content = null ) { extract( shortcode_atts( array( 'title' => 'Show Press Release', 'type' => 'pressrelease', 'hidden' => 'yes', 'more_text' => 'Show', 'less_text' => 'Hide' ), $atts ) ); // ... logic to determine visibility ... // Vulnerable: Attributes are concatenated into HTML string without escaping $returnstring = '<div id="' . $type . '" class="sh-section">'; $returnstring .= '<div class="sh-title">' . $title . '</div>'; $returnstring .= '<div class="sh-content">' . do_shortcode($content) . '</div>'; return $returnstring; }
Security Fix
@@ -108,7 +108,7 @@ - $returnstring = '<div id="' . $type . '" class="sh-section">'; - $returnstring .= '<div class="sh-title">' . $title . '</div>'; + $returnstring = '<div id="' . esc_attr($type) . '" class="sh-section">'; + $returnstring .= '<div class="sh-title">' . wp_kses_post($title) . '</div>';
Exploit Outline
An attacker with Contributor-level access or higher can exploit this by creating a new post or editing an existing one and inserting a [showhide] shortcode. The attacker populates a shortcode attribute with a payload, such as: [showhide title="<script>alert(document.domain)</script>"]. When the post is published or previewed, the shortcode handler processes the attribute and echoes it directly into the HTML response, causing the browser to execute the injected JavaScript whenever the page is loaded.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.