Happy Addons for Elementor <= 3.21.0 - Insecure Direct Object Reference to Authenticated (Contributor+) Post Duplication via 'post_id' Parameter
Description
The Happy Addons for Elementor plugin for WordPress is vulnerable to Insecure Direct Object Reference in all versions up to, and including, 3.21.0 via the `ha_duplicate_thing` admin action handler. This is due to the `can_clone()` method only checking `current_user_can('edit_posts')` (a general capability) without performing object-level authorization such as `current_user_can('edit_post', $post_id)`, and the nonce being tied to the generic action name `ha_duplicate_thing` rather than to a specific post ID. This makes it possible for authenticated attackers, with Contributor-level access and above, to clone any published post, page, or custom post type by obtaining a valid clone nonce from their own posts and changing the `post_id` parameter to target other users' content. The clone operation copies the full post content, all post metadata (including potentially sensitive widget configurations and API tokens), and taxonomies into a new draft owned by the attacker.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:NTechnical Details
<=3.21.0What Changed in the Fix
Changes introduced in v3.21.1
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-2917 ## 1. Vulnerability Summary The **Happy Addons for Elementor** plugin (up to 3.21.0) contains an **Insecure Direct Object Reference (IDOR)** vulnerability in its post duplication feature. The vulnerability exists within the `Happy_Addons\Elementor\Classes…
Show full research plan
Exploitation Research Plan: CVE-2026-2917
1. Vulnerability Summary
The Happy Addons for Elementor plugin (up to 3.21.0) contains an Insecure Direct Object Reference (IDOR) vulnerability in its post duplication feature. The vulnerability exists within the Happy_Addons\Elementor\Classes\Clone_Handler::duplicate_thing method.
While the plugin checks if the user has the general edit_posts capability (possessed by Contributors), it fails to perform object-level authorization (i.e., checking if the user is allowed to edit the specific post_id being cloned). Furthermore, the WordPress nonce used to protect the action is tied only to the static action string ha_duplicate_thing rather than being unique per post. This allows a Contributor-level user to obtain a valid nonce from one of their own posts and use it to clone any published post or page on the site, effectively gaining access to its full content and metadata.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin.php(Admin Action) - Action:
ha_duplicate_thing - Parameters:
action:ha_duplicate_thingpost_id: The ID of the target post to clone (IDOR target)._wpnonce: A valid nonce for theha_duplicate_thingaction.
- Authentication: Authenticated (Contributor or higher). Contributors have the
edit_postscapability required by thecan_clone()check. - Preconditions: The target post must have a status other than 'private' or 'draft' (unless the attacker is the author), as per the check in
duplicate_thing(). Published posts are the primary targets.
3. Code Flow
- Entry Point: A request is sent to
admin.php?action=ha_duplicate_thing. - Hook Registration: The plugin registers the action handler (likely via
admin_action_ha_duplicate_thing). - Authorization Check (
classes/clone-handler.php:24):public static function can_clone() { return current_user_can( 'edit_posts' ); // Contributor+ passes this } - Parameter Extraction (
classes/clone-handler.php:73-75):$nonce = isset( $_GET['_wpnonce'] ) ? $_GET['_wpnonce'] : ''; $post_id = isset( $_GET['post_id'] ) ? absint( $_GET['post_id'] ) : 0; - Nonce Verification (
classes/clone-handler.php:78):if ( ! wp_verify_nonce( $nonce, self::ACTION ) ) { // self::ACTION = 'ha_duplicate_thing' return; } - Weak Ownership Check (
classes/clone-handler.php:84-90):
Crucially, this check is skipped for 'published' posts.if ( ('private' == $post_status || 'draft' == $post_status) && ! $same_author ) { wp_die( __( 'Sorry, you are not allowed to clone this item.' ) ); } - Sink (
classes/clone-handler.php:97):
The$duplicated_post_id = self::duplicate_post( $post );duplicate_postmethod creates a new draft wherepost_authoris set to the current user (the attacker).
4. Nonce Acquisition Strategy
The nonce is for the action ha_duplicate_thing. Since it is not post-specific, an attacker can extract it from the WordPress admin interface where they have permission to see the "Happy Clone" link.
- Requirement: The attacker must have at least one post they are allowed to edit (e.g., a post they created).
- Steps:
- Log in as a Contributor.
- Create a post:
wp post create --post_type=post --post_status=publish --post_title="Attacker Post" --post_author=[CONTRIBUTOR_ID]. - Navigate to the "Posts" list:
/wp-admin/edit.php. - The "Happy Clone" link is added via
Clone_Handler::add_row_actions(Line 46). - Use
browser_navigateto/wp-admin/edit.php. - Use
browser_evalto find the "Happy Clone" link associated with the attacker's post and extract the_wpnonceparameter from its URL.
5. Exploitation Strategy
- Setup:
- Create an Administrator user.
- Create a Contributor user.
- As Administrator, create a "Sensitive Post" (ID
X) with content and perhaps some custom metadata (simulating sensitive widget data).
- Nonce Extraction:
- As Contributor, create a dummy post (ID
Y). - Navigate to
/wp-admin/edit.php. - Extract the
_wpnoncefrom the "Happy Clone" link for postY.
- As Contributor, create a dummy post (ID
- Trigger IDOR:
- Construct the exploit URL:
/wp-admin/admin.php?action=ha_duplicate_thing&post_id=[X]&_wpnonce=[EXTRACTED_NONCE]. - Send the request using
http_request.
- Construct the exploit URL:
- Validation:
- The response should be a redirect (302) to
edit.php?post_type=post. - Check the post list for a new post titled "Sensitive Post - [Cloned #[X]]".
- Verify the author of the new post is the Contributor.
- The response should be a redirect (302) to
6. Test Data Setup
- Target Post (Admin):
- Title:
Secret Admin Blueprint - Content:
This is restricted content with internal API keys: {key: "12345"} - Status:
publish
- Title:
- Attacker Post (Contributor):
- Title:
My Trash Post - Status:
publish
- Title:
7. Expected Results
- The HTTP request to clone the Admin post (ID
X) returns a 302 redirect. - A new post is created in the database.
- The new post's
post_contentis identical to the Admin post. - The new post's
post_authoris the ID of the Contributor. - The new post's
post_statusisdraft.
8. Verification Steps
- List Posts via CLI:
wp post list --post_author=[CONTRIBUTOR_ID] --post_status=draft --fields=ID,post_title,post_content - Check for Cloned Content:
Compare thepost_contentof the new draft with the original Admin post content. - Verify Metadata:
wp post meta list [NEW_DRAFT_ID]
Check if metadata from the Admin post was successfully copied.
9. Alternative Approaches
- Custom Post Types: If the site uses Elementor Templates (CPT
elementor_library), try cloning a Template ID. These often contain complex JSON in_elementor_datametadata which is copied byduplicate_meta_entries(Line 183). - Ref Parameter: Test the
ref=editorparameter to see if the plugin redirects the Contributor directly into the Elementor editor for the stolen content:/wp-admin/admin.php?action=ha_duplicate_thing&post_id=[X]&_wpnonce=[NONCE]&ref=editor
Summary
The Happy Addons for Elementor plugin is vulnerable to an Insecure Direct Object Reference (IDOR) that allows Contributor-level users and above to clone any published post, page, or custom post type. This vulnerability exists because the plugin checks for general 'edit_posts' capabilities rather than object-specific permissions, and utilizes a generic nonce that is not tied to a specific post ID.
Vulnerable Code
// classes/clone-handler.php:18 public static function can_clone() { return current_user_can( 'edit_posts' ); } --- // classes/clone-handler.php:73 $nonce = isset( $_GET['_wpnonce'] ) ? $_GET['_wpnonce'] : ''; $post_id = isset( $_GET['post_id'] ) ? absint( $_GET['post_id'] ) : 0; $ref = isset( $_GET['ref'] ) ? sanitize_text_field($_GET['ref']) : ''; if ( ! wp_verify_nonce( $nonce, self::ACTION ) ) { return; } $post_status = get_post_status ( $post_id ); $same_author = self::is_the_same_author( $post_id ); if ( ('private' == $post_status || 'draft' == $post_status) && ! $same_author ) { wp_die( __( 'Sorry, you are not allowed to clone this item.' ) ); }
Security Fix
@@ -81,6 +81,10 @@ return; } + if ( ! current_user_can( 'edit_post', $post_id ) ) { + wp_die( __( 'Sorry, you are not allowed to clone this item.' ) ); + } + $post_status = get_post_status ( $post_id ); $same_author = self::is_the_same_author( $post_id );
Exploit Outline
1. Authenticate to the WordPress site as a user with at least Contributor-level privileges. 2. Create or navigate to a post owned by the attacker to view the 'Happy Clone' link in the admin dashboard. 3. Extract the `_wpnonce` value from the 'Happy Clone' link; this nonce is valid for the `ha_duplicate_thing` action across all posts because it is not post-specific. 4. Identify the `post_id` of the target content (e.g., a published post or page authored by an Administrator). 5. Construct an exploit URL by targeting the `admin_action_ha_duplicate_thing` endpoint: `/wp-admin/admin.php?action=ha_duplicate_thing&post_id=[TARGET_ID]&_wpnonce=[EXTRACTED_NONCE]`. 6. Submit the request. The plugin will create a new draft containing the full content and metadata of the target post, with the attacker as the new owner.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.