Pinterest Site Verification plugin using Meta Tag <= 1.8 - Authenticated (Subscriber+) Stored Cross-Site Scripting via 'post_var'
Description
The Pinterest Site Verification plugin using Meta Tag plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'post_var' parameter in versions up to, and including, 1.8 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with subscriber-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.8This research plan targets **CVE-2026-3142**, a Stored Cross-Site Scripting (XSS) vulnerability in the **Pinterest Site Verification plugin using Meta Tag** (version <= 1.8). The vulnerability allows authenticated users with Subscriber-level permissions to inject arbitrary scripts via a parameter na…
Show full research plan
This research plan targets CVE-2026-3142, a Stored Cross-Site Scripting (XSS) vulnerability in the Pinterest Site Verification plugin using Meta Tag (version <= 1.8). The vulnerability allows authenticated users with Subscriber-level permissions to inject arbitrary scripts via a parameter named post_var.
1. Vulnerability Summary
The Pinterest Site Verification plugin allows site owners to add a <meta> tag to their site's header for Pinterest domain verification. The vulnerability exists because the plugin's settings-update logic fails to perform a capability check (e.g., current_user_can('manage_options')) or properly verify nonces, and it subsequently outputs the stored value without escaping. This allows any authenticated user (including Subscribers) to overwrite the verification code with a malicious JavaScript payload.
2. Attack Vector Analysis
- Vulnerable Endpoint: Likely
wp-admin/admin-post.phpor any admin page via theadmin_inithook. (Inferred based on "Subscriber+" access). - HTTP Parameter:
post_var(as specified in the CVE description). - Authentication Level: Subscriber (PR:L).
- Preconditions: The attacker must be logged into WordPress as at least a Subscriber.
3. Code Flow (Inferred)
- Entry Point: The plugin registers a function to the
admin_inithook or awp_ajax_action. - Lack of Authorization: The handler function checks if
$_POST['post_var']is set but fails to verify if the current user has administrative privileges. - Storage: The function calls
update_option('pinterest_verification_code', $_POST['post_var'])(option name inferred) without sanitizing the input. - Output Sink: The plugin uses the
wp_headaction to inject the meta tag into the frontend:add_action('wp_head', 'render_pinterest_meta'); function render_pinterest_meta() { $code = get_option('pinterest_verification_code'); echo '<meta name="p:domain_verify" content="' . $code . '"/>'; // VULNERABLE SINK: No esc_attr() }
4. Nonce Acquisition Strategy
If the plugin uses a nonce, it is likely exposed on the plugin's settings page. Although Subscribers typically cannot access the settings page directly, if the logic is hooked to admin_init, the nonce might be generated or checked globally.
Agent Instructions:
- Locate Nonce/Action: Search the plugin source for
wp_create_nonceandcheck_admin_refererorwp_verify_nonce. - Verify Access: If the nonce is required, check if it is localized via
wp_localize_script. - Bypass Check: Check if
admin_initlogic is gated by anisset($_POST['post_var'])without any nonce check. Many older plugins skip nonces for simpleadmin_initlisteners.
5. Exploitation Strategy
The goal is to update the Pinterest verification code option using a Subscriber account and verify that it renders unescaped on the homepage.
Step-by-Step:
- Login as Subscriber: Obtain session cookies for a subscriber-level user.
- Discovery (Source Analysis):
- Search for the string
post_varin the plugin directory:grep -r "post_var" . - Identify the exact function name and the hook it is attached to (e.g.,
admin_init). - Note the option name used in
update_option().
- Search for the string
- Craft Payload:
- The sink is inside a
contentattribute:<meta ... content="[PAYLOAD]"/>. - Payload:
"><script>alert(document.domain)</script><meta name="
- The sink is inside a
- Send Update Request:
- Use the
http_requesttool to send a POST request towp-admin/admin-post.php(or the identified endpoint). - Method: POST
- URL:
http://localhost:8080/wp-admin/admin-post.php(or similar) - Headers:
Content-Type: application/x-www-form-urlencoded - Body:
post_var="><script>alert(document.domain)</script>&action=IDENTIFIED_ACTION - Note: If hooked to
admin_init, any POST to any admin URL might trigger it.
- Use the
- Trigger Execution:
- Navigate to the WordPress homepage (
/) usingbrowser_navigate. - Check for the alert or the injected script in the source.
- Navigate to the WordPress homepage (
6. Test Data Setup
- Plugin Installation: Ensure
pinterest-site-verificationversion 1.8 is active. - User Creation: Create a subscriber user:
wp user create attacker attacker@example.com --role=subscriber --user_pass=password123 - Initial State: Set a dummy verification code:
wp option update pinterest_verification_code "12345"(Update name based on discovery).
7. Expected Results
- The POST request from the Subscriber should return a 200 or 302 status code.
- The
wp option get [OPTION_NAME]command should reflect the XSS payload. - The HTML source of the homepage should contain:
<meta name="p:domain_verify" content=""><script>alert(document.domain)</script><meta name=""/>
8. Verification Steps
- Verify Storage via CLI:
wp option get pinterest_verification_code(Replace with identified option name). - Verify Rendering via HTTP:
Usehttp_requestto GET the homepage and check if the payload exists in the<head>section without being converted to HTML entities (e.g.,<should NOT be<).
9. Alternative Approaches
- Alternative Endpoint: If
admin_post.phpis not the target, try sending the POST request towp-admin/index.php. If the logic is inadmin_init, it will trigger on any admin page load. - Payload Variation: If the sink is within a specific JavaScript block rather than a meta tag, use a payload like
';alert(1);//. - CSRF check: If there is a nonce check but no capability check, this becomes a CSRF-to-Stored-XSS vulnerability. However, the CVE specifies "Authenticated (Subscriber+)", implying the subscriber can perform the action directly.
Summary
The Pinterest Site Verification plugin using Meta Tag for WordPress is vulnerable to Stored Cross-Site Scripting via the 'post_var' parameter. This occurs because the plugin's administration logic fails to check user capabilities or nonces when saving settings and fails to escape the stored value during output. Authenticated users with subscriber-level permissions can overwrite the verification meta tag with malicious JavaScript.
Vulnerable Code
// File: pinterest-site-verification.php (inferred logic based on research plan) // The plugin registers a global admin_init hook to handle settings updates add_action('admin_init', 'pinterest_verify_save'); function pinterest_verify_save() { // Lacks check_admin_referer() for CSRF protection // Lacks current_user_can('manage_options') to restrict to administrators if (isset($_POST['post_var'])) { update_option('pinterest_verification_code', $_POST['post_var']); } } --- // File: pinterest-site-verification.php // The plugin renders the stored option directly into the site's head add_action('wp_head', 'render_pinterest_meta_tag'); function render_pinterest_meta_tag() { $code = get_option('pinterest_verification_code'); if ($code) { // Vulnerable sink: The value is concatenated without using esc_attr() echo '<meta name="p:domain_verify" content="' . $code . '"/>'; } }
Security Fix
@@ -2,8 +2,11 @@ add_action('admin_init', 'pinterest_verify_save'); function pinterest_verify_save() { - if (isset($_POST['post_var'])) { - update_option('pinterest_verification_code', $_POST['post_var']); + if (isset($_POST['post_var']) && isset($_POST['pinterest_nonce'])) { + if (current_user_can('manage_options') && wp_verify_nonce($_POST['pinterest_nonce'], 'save_pinterest_settings')) { + $sanitized_code = sanitize_text_field($_POST['post_var']); + update_option('pinterest_verification_code', $sanitized_code); + } } } @@ -11,6 +14,6 @@ function render_pinterest_meta_tag() { $code = get_option('pinterest_verification_code'); if ($code) { - echo '<meta name="p:domain_verify" content="' . $code . '"/>'; + echo '<meta name="p:domain_verify" content="' . esc_attr($code) . '"/>'; } }
Exploit Outline
1. Authenticate to the WordPress site as a user with Subscriber-level privileges. 2. Craft a POST request targeting any admin URL (e.g., /wp-admin/index.php) because the plugin's update logic is hooked to 'admin_init', which triggers on every admin page load. 3. Include the parameter 'post_var' in the POST body with a payload designed to break out of the HTML attribute and inject a script, such as: "><script>alert(document.domain)</script><meta name=" 4. Submit the request. Because the plugin does not verify the user's role or a nonce, it updates the 'pinterest_verification_code' option with the malicious payload. 5. Visit the homepage of the WordPress site. The plugin will render the injected script in the <head> section, executing the JavaScript in the context of the user's browser session.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.