CVE-2026-1279

Employee Directory <= 1.2.1 - Authenticated (Contributor+) Stored Cross-Site Scripting via 'form_title' Shortcode Attribute

mediumImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
6.4
CVSS Score
6.4
CVSS Score
medium
Severity
1.2.2
Patched in
1d
Time to patch

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:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Changed
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=1.2.1
PublishedFebruary 5, 2026
Last updatedFebruary 6, 2026

Source Code

WordPress.org SVN
Research Plan
Unverified

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` 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_title is echoed directly into the HTML of the search form.

3. Code Flow (Inferred)

  1. Registration: The plugin registers the shortcode in the init hook:
    add_shortcode('search_employee_directory', 'render_search_form_callback'); (inferred function name).
  2. 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);
    
  3. 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()
    
  4. 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.

  1. Navigate to Post Editor: Use browser_navigate to http://localhost:8080/wp-admin/post-new.php.
  2. Extract Nonce: Use browser_eval to extract the _wpnonce from the page source.
    • Script: document.querySelector('#_wpnonce').value or wp.apiFetch.nonce if the block editor is used.
  3. Alternative: The agent can simply use browser_type to 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:

  1. Login: Authenticate as a user with the Contributor role.
  2. Create Post: Send a POST request to wp-admin/post.php to create a new post containing the malicious shortcode.
    • Payload: [search_employee_directory form_title='"><img src=x onerror=alert(document.domain)>']
  3. Publish/Save: Ensure the post status is set to publish (if allowed) or pending (if the attacker is a Contributor). Even in "Pending Review," an Administrator viewing the preview will trigger the XSS.
  4. 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: editpost
    • post_ID: [ID_FROM_STEP_2]
    • _wpnonce: [EXTRACTED_NONCE]
    • post_title: XSS Test
    • content: [search_employee_directory form_title='"><img src=x onerror=alert(document.domain)>']
    • publish: Publish (or save for Contributor)

6. Test Data Setup

  1. Install Plugin: Ensure employee-staff-directory version 1.2.1 is installed and active.
  2. Create Contributor:
    wp user create attacker attacker@example.com --role=contributor --user_pass=password
    
  3. Target Page: No specific settings are required, as the vulnerability is triggered by the shortcode itself.

7. Expected Results

  1. The post will be saved successfully.
  2. 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).
  3. A JavaScript alert box displaying the document domain will appear.

8. Verification Steps (Post-Exploit)

  1. Check Post Content via CLI:
    wp post list --post_type=post --fields=ID,post_content
    
    Verify the shortcode with the payload exists in the database.
  2. Verify Frontend Output:
    Use http_request to 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_title is 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.php using 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].
Research Findings
Static analysis — not yet PoC-verified

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

--- a/employee-staff-directory/includes/public-functions.php
+++ b/employee-staff-directory/includes/public-functions.php
@@ -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.