CVE-2026-1217

Yoast Duplicate Post <= 4.5 - Authenticated (Contributor+) Missing Authorization to Arbitrary Post Duplication and Overwrite

mediumMissing Authorization
5.4
CVSS Score
5.4
CVSS Score
medium
Severity
4.6
Patched in
1d
Time to patch

Description

The Yoast Duplicate Post plugin for WordPress is vulnerable to unauthorized modification of data due to a missing capability check on the clone_bulk_action_handler() and republish_request() functions in all versions up to, and including, 4.5. This makes it possible for authenticated attackers, with Contributor-level access and above, to duplicate any post on the site including private, draft, and trashed posts they shouldn't have access to. Additionally, attackers with Author-level access and above can use the Rewrite & Republish feature to overwrite any published post with their own content.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=4.5
PublishedMarch 17, 2026
Last updatedMarch 18, 2026
Affected pluginduplicate-post

What Changed in the Fix

Changes introduced in v4.6

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Research Plan: CVE-2026-1217 - Yoast Duplicate Post Authorization Bypass ## 1. Vulnerability Summary The **Yoast Duplicate Post** plugin (<= 4.5) contains a missing authorization vulnerability in its post duplication and "Rewrite & Republish" features. While the plugin intends to restrict these c…

Show full research plan

Research Plan: CVE-2026-1217 - Yoast Duplicate Post Authorization Bypass

1. Vulnerability Summary

The Yoast Duplicate Post plugin (<= 4.5) contains a missing authorization vulnerability in its post duplication and "Rewrite & Republish" features. While the plugin intends to restrict these capabilities to specific roles (Editor and Administrator) by granting them a custom copy_posts capability, the functions clone_bulk_action_handler() and republish_request() fail to verify if the current user has sufficient permissions for the specific target post (or the capability in general).

This allows:

  1. Contributors+ to duplicate any post (including private, draft, or trashed posts of other users) into a new draft they control.
  2. Authors+ to use the "Rewrite & Republish" feature on any published post (even those owned by Administrators), allowing them to eventually overwrite the original content with their own.

2. Attack Vector Analysis

  • Endpoints:
    • wp-admin/admin.php (Single post duplication/rewrite)
    • wp-admin/edit.php (Bulk action duplication)
  • Actions:
    • duplicate_post_save_as_new_draft (Single duplication)
    • duplicate_post_rewrite (Initiate Rewrite & Republish)
    • duplicate_post_clone (Bulk duplication)
  • Parameters:
    • post: The Target ID of the post to duplicate or rewrite.
    • _wpnonce: Security nonce (Action-specific).
  • Authentication: Authenticated user with at least Contributor (for duplication) or Author (for rewriting) roles.
  • Preconditions: The attacker must know the ID of the target post they wish to duplicate/overwrite.

3. Code Flow

  1. Entry Point: A request is made to wp-admin/admin.php with action=duplicate_post_save_as_new_draft and a target post ID.
  2. Hook Registration: In admin-functions.php, the plugin initializes via duplicate_post_admin_init(). The handlers for duplication are registered (typically via admin_action_ or load-edit.php hooks).
  3. Vulnerable Function: republish_request() (for rewriting) or the logic inside clone_bulk_action_handler() executes.
  4. Authorization Failure: The functions perform a generic nonce check but fail to call current_user_can( 'copy_posts' ) or current_user_can( 'edit_post', $post_id ).
  5. Sink: The code proceeds to duplicate_post_create_duplicate() (in common-functions.php / Utils) which performs the SQL operations to clone the post data into a new entry.

4. Nonce Acquisition Strategy

The plugin localizes nonces and pre-built links into the duplicatePost JavaScript object on post-related admin pages.

  1. Create a post: As the attacker (Contributor or Author), create a simple post of your own.
  2. Navigate to Edit Page: Navigate to the edit screen of your own post.
  3. Extract Nonce via Browser:
    Use browser_eval to extract the links containing nonces from the localized duplicatePost object.
    • Variable: window.duplicatePost
    • Keys: newDraftLink (for duplication) or rewriteAndRepublishLink (for rewriting).
// Example Extraction
const newDraftUrl = window.duplicatePost?.newDraftLink; 
// Result: ".../wp-admin/admin.php?action=duplicate_post_save_as_new_draft&post=MY_ID&_wpnonce=abc12345"

The nonces in this plugin for these specific actions are generally not ID-specific (they are action-specific), meaning a nonce generated for post=MY_ID will work when sent with post=TARGET_ID.

5. Exploitation Strategy

Attack A: Unauthorized Duplication (Contributor)

  1. Role: Contributor.
  2. Identify Target: Locate a Private or Draft post ID created by an Administrator (e.g., ID 10).
  3. Get Nonce: Extract _wpnonce from the Contributor's own post edit screen as described in Section 4.
  4. Forged Request:
    • URL: http://vulnerable.test/wp-admin/admin.php?action=duplicate_post_save_as_new_draft&post=10&_wpnonce=[EXTRACTED_NONCE]
    • Method: GET
  5. Result: A new draft is created for the Contributor containing the content of the Admin's private post.

Attack B: Overwriting Admin Posts (Author)

  1. Role: Author.
  2. Identify Target: A published post ID created by an Administrator (e.g., ID 20).
  3. Get Nonce: Extract _wpnonce for duplicate_post_rewrite from the Author's own post.
  4. Forged Request (Initiation):
    • URL: http://vulnerable.test/wp-admin/admin.php?action=duplicate_post_rewrite&post=20&_wpnonce=[EXTRACTED_NONCE]
    • Method: GET
  5. Execution: This creates a "Rewrite & Republish" copy (status: dp-rewrite-republish). The Author is redirected to edit this copy.
  6. Finalize Overwrite: The Author edits the copy and clicks "Republish". Because of the missing check in the republish handler, the plugin overwrites the Administrator's original post (ID 20) with the Author's modified content.

6. Test Data Setup

  1. Administrator:
    • Create a Private Post: wp post create --post_title="Admin Secret" --post_status=private --post_content="CONFIDENTIAL DATA" (ID 1).
    • Create a Published Post: wp post create --post_title="Official News" --post_status=publish --post_content="Original content." (ID 2).
  2. Contributor: Create user attacker_c with role contributor. Create one post for them to access nonces.
  3. Author: Create user attacker_a with role author. Create one post for them to access nonces.

7. Expected Results

  • Duplication: The Contributor receives a 302 Redirect to the edit screen of a newly created post (e.g., ID 3). This new post has the same content as the Admin's private post (ID 1).
  • Rewrite: The Author receives a 302 Redirect to a new post with status dp-rewrite-republish. After "Republishing," the original post (ID 2) now lists the Author as the contributor of the content and contains the Author's modifications.

8. Verification Steps

  1. Verify Duplication:
    wp post get [NEW_ID] --field=post_content
    # Should match "CONFIDENTIAL DATA"
    wp post get [NEW_ID] --field=post_author
    # Should match Attacker's User ID
    
  2. Verify Overwrite:
    wp post get 2 --field=post_content
    # Should show the Author's modified content, NOT "Original content."
    

9. Alternative Approaches

If the duplicatePost JS object is missing from the edit screen, the nonce can be found in the "Bulk Actions" dropdown menu on the edit.php (Post List) screen.

  • The bulk action nonce is usually found in the _wpnonce parameter of the form at #posts-filter.
  • The bulk action name is duplicate_post_clone.
  • Request: wp-admin/edit.php?post_type=post&action=duplicate_post_clone&post[]=1&_wpnonce=[NONCE]
Research Findings
Static analysis — not yet PoC-verified

Summary

The Yoast Duplicate Post plugin (<= 4.5) is vulnerable to unauthorized modification of data due to missing capability and ownership checks in its post duplication and 'Rewrite & Republish' features. This allows Contributors to clone private or draft posts they shouldn't access, and Authors to overwrite any published post (including those by Administrators) with their own content.

Vulnerable Code

// admin-functions.php @ 4.5 (Initialization logic lacking capability guards for specific actions)
function duplicate_post_admin_init() {
	duplicate_post_plugin_upgrade();

	if ( intval( get_site_option( 'duplicate_post_show_notice' ) ) === 1 ) {
		if ( is_multisite() ) {
			add_action( 'network_admin_notices', 'duplicate_post_show_update_notice' );
		}
		else {
			add_action( 'admin_notices', 'duplicate_post_show_update_notice' );
		}
		add_action( 'wp_ajax_duplicate_post_dismiss_notice', 'duplicate_post_dismiss_notice' );
	}

	add_action( 'dp_duplicate_post', 'duplicate_post_copy_post_meta_info', 10, 2 );
	add_action( 'dp_duplicate_page', 'duplicate_post_copy_post_meta_info', 10, 2 );

	if ( intval( get_option( 'duplicate_post_copychildren' ) ) === 1 ) {
		add_action( 'dp_duplicate_post', 'duplicate_post_copy_children', 20, 3 );
		add_action( 'dp_duplicate_page', 'duplicate_post_copy_children', 20, 3 );
	}

---

// admin-functions.php @ 4.5 (Intended authorization model via 'copy_posts' capability)
	if ( empty( $installed_version ) ) {
		// Get default roles.
		$default_roles = [
			'editor',
			'administrator',
			'wpseo_manager',
			'wpseo_editor',
		];

		foreach ( $default_roles as $name ) {
			$role = get_role( $name );
			if ( ! empty( $role ) ) {
				$role->add_cap( 'copy_posts' );
			}
		}

Security Fix

--- /home/deploy/wp-safety.org/data/plugin-versions/duplicate-post/4.5/admin-functions.php	2022-01-14 10:46:00.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/duplicate-post/4.6/admin-functions.php	2026-02-18 11:29:52.000000000 +0000
@@ -11,6 +11,7 @@
 }
 
 use Yoast\WP\Duplicate_Post\UI\Newsletter;
+use Yoast\WP\Duplicate_Post\Utils;
 
 require_once DUPLICATE_POST_PATH . 'options.php';
 
@@ -19,6 +20,8 @@
 
 /**
  * Wrapper for the option 'duplicate_post_version'.
+
+ * @return mixed
  */
 function duplicate_post_get_installed_version() {
 	return get_option( 'duplicate_post_version' );
@@ -26,6 +29,8 @@
 
 /**
  * Wrapper for the defined constant DUPLICATE_POST_CURRENT_VERSION.
+
+ * @return string
  */
 function duplicate_post_get_current_version() {
 	return DUPLICATE_POST_CURRENT_VERSION;
@@ -35,6 +40,8 @@
 
 /**
  * Adds handlers depending on the options.
+
+ * @return void
  */
 function duplicate_post_admin_init() {
 	duplicate_post_plugin_upgrade();
@@ -49,32 +56,29 @@
 		add_action( 'wp_ajax_duplicate_post_dismiss_notice', 'duplicate_post_dismiss_notice' );
 	}
 
-	add_action( 'dp_duplicate_post', 'duplicate_post_copy_post_meta_info', 10, 2 );
-	add_action( 'dp_duplicate_page', 'duplicate_post_copy_post_meta_info', 10, 2 );
+	add_action( 'duplicate_post_after_duplicated', 'duplicate_post_copy_post_meta_info', 10, 2 );
 
 	if ( intval( get_option( 'duplicate_post_copychildren' ) ) === 1 ) {
-		add_action( 'dp_duplicate_post', 'duplicate_post_copy_children', 20, 3 );
-		add_action( 'dp_duplicate_page', 'duplicate_post_copy_children', 20, 3 );
+		add_action( 'duplicate_post_after_duplicated', 'duplicate_post_copy_children', 20, 3 );
 	}
 
 	if ( intval( get_option( 'duplicate_post_copyattachments' ) ) === 1 ) {
-		add_action( 'dp_duplicate_post', 'duplicate_post_copy_attachments', 30, 2 );
-		add_action( 'dp_duplicate_page', 'duplicate_post_copy_attachments', 30, 2 );
+		add_action( 'duplicate_post_after_duplicated', 'duplicate_post_copy_attachments', 30, 2 );
 	}
 
 	if ( intval( get_option( 'duplicate_post_copycomments' ) ) === 1 ) {
-		add_action( 'dp_duplicate_post', 'duplicate_post_copy_comments', 40, 2 );
-		add_action( 'dp_duplicate_page', 'duplicate_post_copy_comments', 40, 2 );
+		add_action( 'duplicate_post_after_duplicated', 'duplicate_post_copy_comments', 40, 2 );
 	}
 
-	add_action( 'dp_duplicate_post', 'duplicate_post_copy_post_taxonomies', 50, 2 );
-	add_action( 'dp_duplicate_page', 'duplicate_post_copy_post_taxonomies', 50, 2 );
+	add_action( 'duplicate_post_after_duplicated', 'duplicate_post_copy_post_taxonomies', 50, 2 );
 
 	add_filter( 'plugin_row_meta', 'duplicate_post_add_plugin_links', 10, 2 );
 }

Exploit Outline

The exploit relies on the fact that duplication and rewrite nonces are action-specific but not tied to specific post IDs or ownership. 1. **Authentication**: An attacker authenticates with a Contributor or Author role. 2. **Nonce Extraction**: The attacker extracts a valid `_wpnonce` for the `duplicate_post_save_as_new_draft` or `duplicate_post_rewrite` actions. This can be done by inspecting the `window.duplicatePost` JavaScript object or bulk action elements on the attacker's own post edit screens. 3. **ID Identification**: The attacker identifies the Target Post ID of a post they do not own or shouldn't have access to (e.g., a Private post or an Administrator's post). 4. **Duplication Attack (Contributor)**: The attacker sends a GET request to `/wp-admin/admin.php?action=duplicate_post_save_as_new_draft&post=[TARGET_ID]&_wpnonce=[NONCE]`. The plugin clones the target post into a new draft owned by the Contributor. 5. **Overwrite Attack (Author)**: The attacker sends a GET request to `/wp-admin/admin.php?action=duplicate_post_rewrite&post=[TARGET_ID]&_wpnonce=[NONCE]`. This creates a 'Rewrite & Republish' draft of the target post. The Author can then modify this draft and use the 'Republish' function to overwrite the original content, as the plugin fails to verify if the Author has the right to edit the original post.

Check if your site is affected.

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